From db2a0e5331d303a2d3d6d7571084ab687a08cd60 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Fri, 14 Dec 2018 17:50:33 +0500 Subject: [PATCH 01/26] Added(autotune-reductor): backport from carbon reductor 7 (#215) - strongbash compatible - better logging - VLAN's are not a subject of tuning - --help added - compatible with CR7 & CR8 only - Incremented version --- setup.py | 2 +- utils/autotune-reductor | 67 ++++++++++++++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 6de94db..a17d9a9 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ def read(*paths): setuptools.setup( name='netutils-linux', - version='2.7.9', + version='2.7.10', author='Oleg Strizhechenko', author_email='oleg.strizhechenko@gmail.com', license='MIT', diff --git a/utils/autotune-reductor b/utils/autotune-reductor index 0ed2ca3..1498fb1 100755 --- a/utils/autotune-reductor +++ b/utils/autotune-reductor @@ -1,24 +1,48 @@ -#!/usr/bin/env bash +#!/bin/bash -set -euo pipefail +set -euE + +if [ -f /usr/local/Reductor/etc/const ]; then + . /usr/local/Reductor/etc/const +elif [ -f /app/reductor/usr/local/Reductor/etc/const ]; then + . /app/reductor/usr/local/Reductor/etc/const +else + echo "$0 $@ [$$] FAILURE no etc/const" >&2 + exit 1 +fi + +echo "$0 $@ [$$] START" >&2 + + +if [ "${1:-}" = "--help" ]; then + echo "Info: автоматические настраивает сетевые карты для приёма зеркала" + echo "Usage: $0 без аргументов" + echo "Example: $0 без аргументов" + exit 0 +fi find_mirror_conf() { - local CR7=/usr/local/Reductor/userinfo/mirror_info.conf - local CR8_INNER=/cfg/userinfo/mirror_info.conf - local CR8_OUTER=/app/reductor/$CR8_INNER + set -e + local cr7="/usr/local/Reductor/userinfo/mirror_info.conf" + local cr8_inner="/cfg/userinfo/mirror_info.conf" + local cr8_outer="/app/reductor/$cr8_inner" local conf - for conf in $CR7 $CR8_INNER $CR8_OUTER; do - [ -f $conf ] || continue - echo $conf + for conf in "$cr7" "$cr8_inner" "$cr8_outer"; do + [ ! -f "$conf" ] && continue + echo "$conf" break done + return 0 } mirror_devices_bridge() { - brctl show | tail -n +2 | awk '{print $4}' + # skip strongbash034 + brctl show | tail -n +2 | awk '{print $4}' | cut -d '.' -f1 | sort -u + return 0 } mirror_devices() { + set -e local mirror_conf mirror_conf="$(find_mirror_conf)" if [ -n "${mirror_conf:-}" ]; then @@ -26,14 +50,35 @@ mirror_devices() { else mirror_devices_bridge fi + return 0 } main() { - maximize-cpu-freq + local device + # virtualenv нужен только для CR7 + if [ ! -f /cfg/config ]; then + if [ ! -f ${VENVBIN}/activate ]; then + echo "$0 $@ [$$] FAILURE no virtualenv" >&2 + exit 2 + fi + set +eu + . ${VENVBIN}/activate + set -eu + fi + maximize-cpu-freq || true for device in $(mirror_devices); do - autorps "$device" || true + echo "$0 $@ [$$] Настройка $device" >&2 + autorps "$device" 2>/dev/null || true rx-buffers-increase "$device" || true done + if [ ! -f /cfg/config ]; then + set +eu + deactivate + set -eu + fi + return 0 } main "$@" +echo "$0 $@ [$$] SUCCESS" >&2 +exit 0 From 1e75627eb4be7fbe1cc71e406cfbeb678c070ebe Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 24 Dec 2018 13:49:54 +0500 Subject: [PATCH 02/26] Added(utils): nic-model-list - show models of network cards. (#216) * Added(utils): nic-model-list - show models of network cards. * Incremented version --- setup.py | 2 +- utils/nic-model-list | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100755 utils/nic-model-list diff --git a/setup.py b/setup.py index a17d9a9..18f1066 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ def read(*paths): setuptools.setup( name='netutils-linux', - version='2.7.10', + version='2.7.11', author='Oleg Strizhechenko', author_email='oleg.strizhechenko@gmail.com', license='MIT', diff --git a/utils/nic-model-list b/utils/nic-model-list new file mode 100755 index 0000000..bf6c3c1 --- /dev/null +++ b/utils/nic-model-list @@ -0,0 +1,50 @@ +#!/bin/bash + +set -eu +# echo "$0 $@ [$$] START" >&2 +declare DEVICE_REGEX="(eth|em|en|bond|p[0-9]*p)[0-9\.]+" + +if [ "{1:-}" == '--help' ]; then + echo "Info: $0 показывает модели сетевых карт в системе" + echo "Usage: $0 без параметров" + echo "Example: $0" + exit 0 +fi + +rx_buf() { + ethtool -g "$1" 2>/dev/null | tac | grep "RX:" | egrep -o "[0-9]+" | tr '\n' '/'| sed 's|/$||g' + return 0 +} + +show_device() { + local eth="$1" + local id="$2" + is_vlan=0 + if ethtool -i "$eth" | grep -q '802.1Q VLAN Support'; then + is_vlan=1 + fi + if [ "$is_vlan" = 0 ]; then + + echo "$eth: $(grep -w $id /tmp/lspci.tmp) rx: $(rx_buf "$eth")" + else + echo "$eth: VLAN interface" + fi + return 0 +} + +main() { + local eth + local id + for eth in $(egrep -o "$DEVICE_REGEX" /proc/net/dev | sort -u); do + for id in $(ethtool -i "$eth" | grep bus-info | sed 's/.*0000://'); do + show_device "$eth" "$id" + done + done + return 0 +} + +lspci > /tmp/lspci.tmp +main + +# echo "$0 $@ [$$] SUCCESS" +exit 0 From 5347c1224cd5355754294bd4c4f4826de0b0eb7a Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 24 Dec 2018 13:55:45 +0500 Subject: [PATCH 03/26] Refactoring(netdev-model-list): strongbash compatible. --- utils/{nic-model-list => netdev-model-list} | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) rename utils/{nic-model-list => netdev-model-list} (71%) diff --git a/utils/nic-model-list b/utils/netdev-model-list similarity index 71% rename from utils/nic-model-list rename to utils/netdev-model-list index bf6c3c1..df4eee2 100755 --- a/utils/nic-model-list +++ b/utils/netdev-model-list @@ -12,6 +12,8 @@ if [ "{1:-}" == '--help' ]; then fi rx_buf() { + set -e + # skip strongbash034 ethtool -g "$1" 2>/dev/null | tac | grep "RX:" | egrep -o "[0-9]+" | tr '\n' '/'| sed 's|/$||g' return 0 } @@ -19,13 +21,12 @@ rx_buf() { show_device() { local eth="$1" local id="$2" - is_vlan=0 - if ethtool -i "$eth" | grep -q '802.1Q VLAN Support'; then - is_vlan=1 - fi + local is_vlan=0 + local output + output="$(ethtool -i "$eth")" + grep -q '802.1Q VLAN Support' <<< "$output" && is_vlan=1 if [ "$is_vlan" = 0 ]; then - - echo "$eth: $(grep -w $id /tmp/lspci.tmp) rx: $(rx_buf "$eth")" + echo "$eth: $(grep -w "$id" /tmp/lspci.tmp) rx: $(rx_buf "$eth")" else echo "$eth: VLAN interface" fi @@ -35,8 +36,8 @@ show_device() { main() { local eth local id - for eth in $(egrep -o "$DEVICE_REGEX" /proc/net/dev | sort -u); do - for id in $(ethtool -i "$eth" | grep bus-info | sed 's/.*0000://'); do + for eth in $(egrep -o "$DEVICE_REGEX" /proc/net/dev | sort -u || true); do + for id in $(ethtool -i "$eth" | grep bus-info | sed 's/.*0000://' || true); do show_device "$eth" "$id" done done From 1cbbbef27c54ff4f6f5a486b702be58d36e03782 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 21 Jan 2019 13:37:06 +0500 Subject: [PATCH 04/26] Added(autotune-reductor): optional automatic RSS tune Also autorps doesn't hide his stderr anymore (because errors now human-readable) --- utils/autotune-reductor | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/autotune-reductor b/utils/autotune-reductor index 1498fb1..bf11761 100755 --- a/utils/autotune-reductor +++ b/utils/autotune-reductor @@ -66,9 +66,13 @@ main() { set -eu fi maximize-cpu-freq || true + . "$CONFIG" for device in $(mirror_devices); do echo "$0 $@ [$$] Настройка $device" >&2 - autorps "$device" 2>/dev/null || true + autorps "$device" || true + if [ "${misc['rss']:-0}" = '1' ]; then + rss-ladder "$device" || true + fi rx-buffers-increase "$device" || true done if [ ! -f /cfg/config ]; then From fe7113e7eb47462dbc5f250bf59cbbb572a0ebd5 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Fri, 20 Dec 2019 17:36:37 +0500 Subject: [PATCH 05/26] Added: FAQ (perf) --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index dece638..42c2f82 100644 --- a/README.rst +++ b/README.rst @@ -312,6 +312,12 @@ Device's detailed rating Hypervisor vendor: 1 Virtualization type: 1 +FAQ +=== + +Q: I see that workload is distributed fine, but there is a lot of workload. How to go deeper, how to understand what my system doing right now? +A: Try `perf top` + How to contribute? ================== From 48b4fa78682ac2eb16687756ce1a8adbb49f2002 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Fri, 20 Dec 2019 17:36:57 +0500 Subject: [PATCH 06/26] Update README.rst --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 42c2f82..c9775da 100644 --- a/README.rst +++ b/README.rst @@ -316,6 +316,7 @@ FAQ === Q: I see that workload is distributed fine, but there is a lot of workload. How to go deeper, how to understand what my system doing right now? + A: Try `perf top` How to contribute? From b958622c5ba2a1ce7839e91e6b3478bf462e3050 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Fri, 20 Dec 2019 17:37:20 +0500 Subject: [PATCH 07/26] Update README.rst --- README.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.rst b/README.rst index c9775da..3aa688d 100644 --- a/README.rst +++ b/README.rst @@ -325,9 +325,3 @@ How to contribute? Close issues ------------ Any help is welcome. Just comment an issue with "I want to help, how can I solve this issue?" to start. - -or donate ---------- -- https://patreon.com/strizhechenko -- https://rocketbank.ru/oleg.strizhechenko -- https://paypal.me/weirded From a2ce13b5dca7af25ddd0c7e50e9207b2dcf61159 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Fri, 20 Dec 2019 17:39:42 +0500 Subject: [PATCH 08/26] Update README.rst --- README.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3aa688d..f4eb218 100644 --- a/README.rst +++ b/README.rst @@ -317,7 +317,12 @@ FAQ Q: I see that workload is distributed fine, but there is a lot of workload. How to go deeper, how to understand what my system doing right now? -A: Try `perf top` +A: Try + +.. code:: shell + + perf top + How to contribute? ================== From f394d675a99beb6010942a104f0c71a32a081e97 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 20 Jan 2020 15:28:33 +0500 Subject: [PATCH 09/26] Fixed(build): froze colorama version to support py3.4 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a0633f3..fc211c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ setuptools pyyaml ipaddress six -colorama +colorama==0.4.3 flake8 pytest collective.checkdocs From 63a3f846271d21b27d0c37a9f4de9cd8bf5ba46a Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 20 Jan 2020 15:34:16 +0500 Subject: [PATCH 10/26] Fixed(build): froze pyyaml version to support py3.4 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fc211c7..40b39ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ prettytable setuptools -pyyaml +pyyaml==5.3 ipaddress six colorama==0.4.3 From 617461a951002ff08320e9a0024c6e4ee035f41a Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 20 Jan 2020 15:40:45 +0500 Subject: [PATCH 11/26] Fixed(build): froze pyyaml version to support py3.4 Strange, pyyaml 5.3 works fine on CentOS 6 with Python 3.4 while it isn't supported in setup.py --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 40b39ee..f1ea17f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ prettytable setuptools -pyyaml==5.3 +pyyaml==5.2 ipaddress six colorama==0.4.3 From 091c15a4b759160215b5f47cfb7de10e9483bc2f Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 20 Jan 2020 15:48:22 +0500 Subject: [PATCH 12/26] Fixed(build): froze colorama version to support py3.4 Strange, colorama 0.4.3 works fine on CentOS 6 with Python 3.4 while it isn't supported in setup.py --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f1ea17f..20e742b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ setuptools pyyaml==5.2 ipaddress six -colorama==0.4.3 +colorama==0.4.2 flake8 pytest collective.checkdocs From 18fe4fd4448019a39f235949f8f69eef7edd23f3 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 20 Jan 2020 15:53:46 +0500 Subject: [PATCH 13/26] Fixed(build): froze colorama version to support py3.4 Strange, colorama 0.4.2 works fine on CentOS 6 with Python 3.4 while it isn't supported in setup.py --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 20e742b..84f6199 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ setuptools pyyaml==5.2 ipaddress six -colorama==0.4.2 +colorama==0.4.1 flake8 pytest collective.checkdocs From 83e80469cea074e3dad3a0119165514c7438d6d7 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Mon, 20 Jan 2020 15:59:05 +0500 Subject: [PATCH 14/26] Deleted(banners): landscape is broken --- README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.rst b/README.rst index f4eb218..7215385 100644 --- a/README.rst +++ b/README.rst @@ -3,8 +3,6 @@ netutils-linux .. |travis| image:: https://travis-ci.org/strizhechenko/netutils-linux.svg?branch=master :target: https://travis-ci.org/strizhechenko/netutils-linux -.. |landscape| image:: https://landscape.io/github/strizhechenko/netutils-linux/master/landscape.svg?style=flat - :target: https://landscape.io/github/strizhechenko/netutils-linux/master .. |pypi| image:: https://badge.fury.io/py/netutils-linux.svg :target: https://badge.fury.io/py/netutils-linux .. |license| image:: https://img.shields.io/badge/License-MIT-yellow.svg?colorB=green @@ -16,7 +14,7 @@ netutils-linux .. |codeclimate| image:: https://img.shields.io/codeclimate/github/strizhechenko/netutils-linux.svg :target: https://codeclimate.com/github/strizhechenko/netutils-linux -|travis| |landscape| |pypi| |license| |pyversions| |codeclimate| |issues| +|travis| |pypi| |license| |pyversions| |codeclimate| |issues| It's a useful utils to simplify Linux network troubleshooting and performance tuning, developed in order to help `Carbon Reductor`_ techsupport and automate the whole linux performance tuning process out of box (ok, except the best RSS layout detection with multiple network devices). These utils may be useful for datacenters and internet service providers with heavy network workload (you probably wouldn't see an effect at your desktop computer). It's now in production usage with 300+ deployment and save us a lot of time with hardware and software settings debugging. Inspired by `packagecloud's blog post`_. From dfcb0900ffffa3020656338bbab123f6da00a168 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Wed, 22 Jan 2020 01:59:19 +0500 Subject: [PATCH 15/26] Development status now is production/stable. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 18f1066..62051d8 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,8 @@ def read(*paths): scripts=[os.path.join('utils/', script) for script in os.listdir('utils/')], install_requires=['pyyaml', 'ipaddress', 'six', 'colorama', 'prettytable', 'argparse'], classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', 'Intended Audience :: Developers', 'Operating System :: MacOS', 'Operating System :: POSIX', From 9561efe49edd0ab3db9496738167b6813e23489b Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Fri, 19 Feb 2021 07:27:35 +0500 Subject: [PATCH 16/26] Added(yaml): specified SafeLoader everywhere to avoid warning (#220) --- netutils_linux_hardware/disk.py | 3 ++- netutils_linux_hardware/memory.py | 3 ++- netutils_linux_hardware/parser.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/netutils_linux_hardware/disk.py b/netutils_linux_hardware/disk.py index ad86df3..d6d0c8c 100644 --- a/netutils_linux_hardware/disk.py +++ b/netutils_linux_hardware/disk.py @@ -68,7 +68,8 @@ def parse(text): types = ['SSD', 'HDD'] if not text: return dict() - data = yaml.load(text.replace(':', ': ').replace('/sys/block/', '').replace('/queue/rotational', '')) + _text = text.replace(':', ': ').replace('/sys/block/', '').replace('/queue/rotational', '') + data = yaml.load(_text, yaml.loader.SafeLoader) return dict((k, types[v]) for k, v in data.items()) class DiskSizeInfo(Parser): diff --git a/netutils_linux_hardware/memory.py b/netutils_linux_hardware/memory.py index 20c5af9..14ba619 100644 --- a/netutils_linux_hardware/memory.py +++ b/netutils_linux_hardware/memory.py @@ -59,7 +59,8 @@ class MemInfo(YAMLLike): ) def parse(self, text): - return dict((k, int(v.replace(' kB', ''))) for k, v in iteritems(yaml.load(text)) if k in self.keys_required) + data = yaml.load(text, yaml.loader.SafeLoader) + return dict((k, int(v.replace(' kB', ''))) for k, v in iteritems(data) if k in self.keys_required) class MemInfoDMIDevice(object): diff --git a/netutils_linux_hardware/parser.py b/netutils_linux_hardware/parser.py index d7ca3db..7f44de1 100644 --- a/netutils_linux_hardware/parser.py +++ b/netutils_linux_hardware/parser.py @@ -25,4 +25,4 @@ def parse_file_safe(self, filepath, **kwargs): class YAMLLike(Parser): @staticmethod def parse(text): - return yaml.load(text) + return yaml.load(text, yaml.loader.SafeLoader) From fc3bc7ee67b9a211dab7800cfd53452dcb537a49 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Fri, 8 Apr 2022 12:14:59 +0500 Subject: [PATCH 17/26] Update BUGS.md --- BUGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BUGS.md b/BUGS.md index 304c7e8..a88e1c7 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,3 +1,4 @@ These "bugs" will never be fixed: - No `bridge` util in CentOS 6.4 (`iproute-2.6.32-23.el6.x86_64`). Solution: `yum -y install iproute` +- If you try to run it on CentOS 7 with python2.7 you may get tracebacks with YAML. Solution is to install dependencies via Yum: `yum install PyYAML.x86_64 python2-pyyaml.noarch python-prettytable.noarch` From f54359ce31e061350993a201be69524fc7a8fec0 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Wed, 18 May 2022 18:21:47 +0500 Subject: [PATCH 18/26] Dependencies: removed frozen version of colorama and pyyaml. Splitted dev deps. --- .travis.yml | 3 ++- requirements-dev.txt | 6 ++++++ requirements.txt | 11 ++--------- setup.py | 2 ++ 4 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 requirements-dev.txt diff --git a/.travis.yml b/.travis.yml index f2ba8ed..0c5d3c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,14 @@ python: - "2.7" - "3.4" - "3.6" + - "3.8" before_install: - gem install fpm - gem install package_cloud - sudo apt-get install rpm -install: pip install --upgrade -r requirements.txt && python setup.py install +install: pip install --upgrade -r requirements.txt && pip install --upgrade -r requirements-dev.txt && python setup.py install script: - make test diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..890ad25 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +pytest +flake8 +collective.checkdocs +coverage +pytest-cov +twine diff --git a/requirements.txt b/requirements.txt index 84f6199..926cf34 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,7 @@ prettytable +pyyaml setuptools -pyyaml==5.2 ipaddress six -colorama==0.4.1 -flake8 -pytest -collective.checkdocs -Pygments +colorama argparse -coverage -pytest-cov -twine diff --git a/setup.py b/setup.py index 62051d8..aa0e9c3 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,8 @@ def read(*paths): 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: Software Development', 'Topic :: Utilities', ], From 90c7ab8f28bd0805ba2f199da4e4a612b507d656 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Wed, 18 May 2022 19:31:25 +0500 Subject: [PATCH 19/26] Refactoring: color usage is strictly follows --no-color flag now. --- netutils_linux_monitoring/base_top.py | 5 ++- netutils_linux_monitoring/colors.py | 49 ++++++++++------------- netutils_linux_monitoring/irqtop.py | 5 +-- netutils_linux_monitoring/link_rate.py | 7 ++-- netutils_linux_monitoring/network_top.py | 5 ++- netutils_linux_monitoring/softirqs.py | 10 ++--- netutils_linux_monitoring/softnet_stat.py | 28 ++----------- netutils_linux_tuning/rss_ladder.py | 2 +- 8 files changed, 42 insertions(+), 69 deletions(-) diff --git a/netutils_linux_monitoring/base_top.py b/netutils_linux_monitoring/base_top.py index ee0ae16..805218e 100644 --- a/netutils_linux_monitoring/base_top.py +++ b/netutils_linux_monitoring/base_top.py @@ -16,7 +16,7 @@ class BaseTop(object): current = None previous = None diff = None - header = Color.wrap('Press CTRL-C to exit...\n', Color.GREY) + header = None options = None file_arg = None file_value = None @@ -113,7 +113,7 @@ def spaces(self, number, sep=' '): def __repr_table__(self, table): if self.options.clear: - return BaseTop.header + str(table) + return self.header + str(table) return str(table) def default_init(self, topology=None): @@ -125,6 +125,7 @@ def default_post_optparse(self): if not self.topology: self.topology = Topology(fake=self.options.random) self.color = Color(self.topology, self.options.color) + self.header = self.color.wrap('Press CTRL-C to exit...\n', self.color.GREY) @abstractmethod def parse(self): diff --git a/netutils_linux_monitoring/colors.py b/netutils_linux_monitoring/colors.py index b0d8999..e40a809 100644 --- a/netutils_linux_monitoring/colors.py +++ b/netutils_linux_monitoring/colors.py @@ -6,23 +6,23 @@ class Color(object): """ Helper for highlighting a console tools """ try: - YELLOW = Fore.LIGHTYELLOW_EX - GREY = Fore.LIGHTBLACK_EX + CLS_YELLOW = Fore.LIGHTYELLOW_EX + CLS_GREY = Fore.LIGHTBLACK_EX except AttributeError: - YELLOW = Fore.YELLOW - GREY = Fore.CYAN + CLS_YELLOW = Fore.YELLOW + CLS_GREY = Fore.CYAN COLORS_NODE = { 0: Fore.GREEN, 1: Fore.RED, - 2: YELLOW, + 2: CLS_YELLOW, 3: Fore.BLUE, -1: Style.RESET_ALL, } COLORS_SOCKET = { 0: Fore.BLUE, - 1: YELLOW, + 1: CLS_YELLOW, 2: Fore.RED, 3: Fore.GREEN, -1: Style.RESET_ALL, @@ -30,40 +30,35 @@ class Color(object): COLOR_NONE = dict((key, "") for key in range(-1, 4)) - def __init__(self, topology, enabled=True): - self.enabled = enabled - self.topology = topology - if topology is not None: - self.color_scheme = self.__choose_color_scheme() + def __init__(self, topology, enabled): + self.enabled, self.topology = enabled, topology + self.RESET, self.RESET_ALL = Fore.RESET, Style.RESET_ALL + self.YELLOW, self.GREY = Color.CLS_YELLOW, Color.CLS_GREY + self.RED, self.BRIGHT = Fore.RED, Style.BRIGHT + self.color_scheme = self.__choose_color_scheme() def __choose_color_scheme(self): - if not self.enabled: - Style.BRIGHT = Style.RESET_ALL = Fore.RED = Fore.RESET = self.GREY = self.YELLOW = "" + if not self.enabled or not self.topology: + self.BRIGHT = self.RESET_ALL = self.RED = self.RESET = self.GREY = self.YELLOW = "" return self.COLOR_NONE if self.topology.layout_kind == 'NUMA': return self.COLORS_NODE return self.COLORS_SOCKET - @staticmethod - def wrap(word, color): + def wrap(self, word, color): """ wrap string in given color """ - return '{0}{1}{2}'.format(color, word, Style.RESET_ALL) + return '{0}{1}{2}'.format(color, word, self.RESET_ALL) - @staticmethod - def colorize(value, warning, error): - return Color.wrap(value, Fore.RED if value >= error else Color.YELLOW if value >= warning else Fore.RESET) + def colorize(self, value, warning, error): + return self.wrap(value, self.RED if value >= error else self.YELLOW if value >= warning else self.RESET) - @staticmethod - def bright(string): - return Color.wrap(string, Style.BRIGHT) + def bright(self, string): + return self.wrap(string, self.BRIGHT) - @staticmethod - def wrap_header(string): - return Color.wrap("# {0}\n".format(string), Style.BRIGHT) + def wrap_header(self, string): + return self.wrap("# {0}\n".format(string), self.BRIGHT) def colorize_cpu(self, cpu): - if not self.color_scheme: - self.color_scheme = self.__choose_color_scheme() if isinstance(cpu, str): cpu = int(cpu[3:]) return self.color_scheme.get(self.topology.layout.get(cpu)) diff --git a/netutils_linux_monitoring/irqtop.py b/netutils_linux_monitoring/irqtop.py index a2904e9..13ce082 100644 --- a/netutils_linux_monitoring/irqtop.py +++ b/netutils_linux_monitoring/irqtop.py @@ -59,10 +59,9 @@ def make_rows(self): output_lines.append(line) return output_lines, cpu_count - @staticmethod - def colorize_irq_per_cpu(irq_per_cpu): + def colorize_irq_per_cpu(self, irq_per_cpu): """ :returns: highlighted by warning/error irq string """ - return Color.colorize(irq_per_cpu, 40000, 80000) + return self.color.colorize(irq_per_cpu, 40000, 80000) def __repr__(self): output_lines, cpu_count = self.make_rows() diff --git a/netutils_linux_monitoring/link_rate.py b/netutils_linux_monitoring/link_rate.py index a94bbd0..880825b 100644 --- a/netutils_linux_monitoring/link_rate.py +++ b/netutils_linux_monitoring/link_rate.py @@ -73,9 +73,8 @@ def eval(self): def make_header(self): return ['Device'] + [stat.shortname for stat in self.stats] - @staticmethod - def colorize_stat(stat, value): - return Color.colorize(value, 1, 1) if 'errors' in stat.filename or 'dropped' in stat.filename else value + def colorize_stat(self, stat, value): + return self.color.colorize(value, 1, 1) if 'errors' in stat.filename or 'dropped' in stat.filename else value def colorize_stats(self, dev, repr_source): return [self.colorize_stat(stat, repr_source[dev][stat]) for stat in self.stats] @@ -86,7 +85,7 @@ def make_rows(self): repr_source = self.repr_source() for dev in self.options.devices: dev_node = self.pci.devices.get(dev) - dev_color = self.color.COLORS_NODE.get(dev_node) + dev_color = self.color.COLORS_NODE.get(dev_node) if self.options.color else "" _dev = self.color.wrap(dev, dev_color) yield [_dev] + self.colorize_stats(dev, repr_source) diff --git a/netutils_linux_monitoring/network_top.py b/netutils_linux_monitoring/network_top.py index cecf246..bfb824d 100644 --- a/netutils_linux_monitoring/network_top.py +++ b/netutils_linux_monitoring/network_top.py @@ -23,7 +23,7 @@ def __init__(self): } self.parse_options() self.topology = Topology(fake=self.options.random, lscpu_output=self.options.lscpu_output) - self.color = Color(self.topology) + self.color = Color(self.topology, self.options.color) for top in self.tops.values(): top.topology = self.topology top.color = self.color @@ -50,7 +50,7 @@ def tick(self): def __repr__(self): output = [ - BaseTop.header, + self.header, self.__repr_irq(), self.__repr_cpu(), self.__repr_dev(), @@ -71,6 +71,7 @@ def parse_options(self): top.options = self.options if hasattr(top, 'post_optparse'): top.post_optparse() + self.default_post_optparse() def __repr_dev(self): top = self.tops.get('link-rate') diff --git a/netutils_linux_monitoring/softirqs.py b/netutils_linux_monitoring/softirqs.py index 6c0d51b..ae97e2d 100644 --- a/netutils_linux_monitoring/softirqs.py +++ b/netutils_linux_monitoring/softirqs.py @@ -41,15 +41,13 @@ def __repr__(self): table = make_table(header, ['l', 'r', 'r'], rows) return self.__repr_table__(table) - @staticmethod - def colorize_net_rx(net_rx): + def colorize_net_rx(self, net_rx): """ :returns: highlighted by warning/error net_rx string """ - return Color.colorize(net_rx, 40000, 80000) + return self.color.colorize(net_rx, 40000, 80000) - @staticmethod - def colorize_net_tx(net_tx): + def colorize_net_tx(self, net_tx): """ :returns: highlighted by warning/error net_tx string """ - return Color.colorize(net_tx, 20000, 30000) + return self.color.colorize(net_tx, 20000, 30000) @staticmethod def __active_cpu_count__(data): diff --git a/netutils_linux_monitoring/softnet_stat.py b/netutils_linux_monitoring/softnet_stat.py index dceea35..927daa6 100644 --- a/netutils_linux_monitoring/softnet_stat.py +++ b/netutils_linux_monitoring/softnet_stat.py @@ -84,29 +84,9 @@ def make_header(): def make_rows(self): return [[ self.color.wrap('CPU{0}'.format(stat.cpu), self.color.colorize_cpu(stat.cpu)), - self.colorize_total(stat.total), - self.colorize_dropped(stat.dropped), - self.colorize_time_squeeze(stat.time_squeeze), - self.colorize_cpu_collision(stat.cpu_collision), + self.color.colorize(stat.total, 300000, 900000), + self.color.colorize(stat.dropped, 1, 1), + self.color.colorize(stat.time_squeeze, 1, 300), + self.color.colorize(stat.cpu_collision, 1, 1000), stat.received_rps ] for stat in self.repr_source()] - - @staticmethod - def colorize_total(total): - """ :returns: highlighted by warning/error total string """ - return Color.colorize(total, 300000, 900000) - - @staticmethod - def colorize_dropped(dropped): - """ :returns: highlighted by warning/error dropped string """ - return Color.colorize(dropped, 1, 1) - - @staticmethod - def colorize_time_squeeze(time_squeeze): - """ :returns: highlighted by warning/error time_squeeze string """ - return Color.colorize(time_squeeze, 1, 300) - - @staticmethod - def colorize_cpu_collision(cpu_collision): - """ :returns: highlighted by warning/error cpu_collision string """ - return Color.colorize(cpu_collision, 1, 1000) diff --git a/netutils_linux_tuning/rss_ladder.py b/netutils_linux_tuning/rss_ladder.py index 27a8f7c..e8dd2d5 100644 --- a/netutils_linux_tuning/rss_ladder.py +++ b/netutils_linux_tuning/rss_ladder.py @@ -65,7 +65,7 @@ def apply(self, decision): if len(set(cpus)) != len(cpus): warning = 'WARNING: some CPUs process multiple queues, consider reduce queue count for this network device' if self.options.color: - print_(self.color.wrap(warning, Color.YELLOW)) + print_(self.color.wrap(warning, self.color.YELLOW)) else: print_(warning) for irq, queue_name, socket_cpu in affinity: From f30e06ca614df6cfe4fe57f40dc4d4bb4e3f25e0 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Wed, 18 May 2022 19:35:54 +0500 Subject: [PATCH 20/26] Fixed: few bugs after refactoring --- netutils_linux_monitoring/base_top.py | 2 +- netutils_linux_monitoring/network_top.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/netutils_linux_monitoring/base_top.py b/netutils_linux_monitoring/base_top.py index 805218e..e062aac 100644 --- a/netutils_linux_monitoring/base_top.py +++ b/netutils_linux_monitoring/base_top.py @@ -119,7 +119,7 @@ def __repr_table__(self, table): def default_init(self, topology=None): BaseTop.__init__(self) self.topology = topology - self.color = Color(self.topology) + # self.color = Color(self.topology, self.options.color) def default_post_optparse(self): if not self.topology: diff --git a/netutils_linux_monitoring/network_top.py b/netutils_linux_monitoring/network_top.py index bfb824d..05c9388 100644 --- a/netutils_linux_monitoring/network_top.py +++ b/netutils_linux_monitoring/network_top.py @@ -102,21 +102,22 @@ def __repr_cpu(self): 'total', 'dropped', 'time_squeeze', 'cpu_collision', 'received_rps', ] fields = [self.color.bright(word) for word in fields] - rows = self.__repr_cpu_make_rows(irqtop, network_output, softirq_top, softnet_stat_top) + rows = self.__repr_cpu_make_rows(irqtop, network_output, softirq_top) table = make_table(fields, ['l'] + ['r'] * (len(fields) - 1), rows) return self.color.wrap_header('Load per cpu:') + str(table) - def __repr_cpu_make_rows(self, irqtop, network_output, softirq_top, softnet_stat_top): + def __repr_cpu_make_rows(self, irqtop, network_output, softirq_top): return [ [ self.color.wrap('CPU{0}'.format(stat.cpu), self.color.colorize_cpu(stat.cpu)), irqtop.colorize_irq_per_cpu(irq), softirq_top.colorize_net_rx(net_rx), softirq_top.colorize_net_tx(net_tx), - softnet_stat_top.colorize_total(stat.total), - softnet_stat_top.colorize_dropped(stat.dropped), - softnet_stat_top.colorize_time_squeeze(stat.time_squeeze), - softnet_stat_top.colorize_cpu_collision(stat.cpu_collision), + # Теперь я вспомнил зачем там были семантичные функции, чтобы не было дублирования + self.color.colorize(stat.total, 300000, 900000), + self.color.colorize(stat.dropped, 1, 1), + self.color.colorize(stat.time_squeeze, 1, 300), + self.color.colorize(stat.cpu_collision, 1, 1000), stat.received_rps ] for irq, net_rx, net_tx, stat in network_output From 3f0ffae46414511bf666ae2069aabdc7af75e550 Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Wed, 18 May 2022 19:41:09 +0500 Subject: [PATCH 21/26] Removed travis CI support as they want a credit card now --- .travis.yml | 21 --------------------- README.rst | 4 +--- setup.py | 2 +- 3 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0c5d3c8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: python - -os: linux - -python: - - "2.7" - - "3.4" - - "3.6" - - "3.8" - -before_install: - - gem install fpm - - gem install package_cloud - - sudo apt-get install rpm - -install: pip install --upgrade -r requirements.txt && pip install --upgrade -r requirements-dev.txt && python setup.py install - -script: - - make test - - make lint - - make rpm_upload diff --git a/README.rst b/README.rst index 7215385..0f0712a 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,6 @@ netutils-linux ============== -.. |travis| image:: https://travis-ci.org/strizhechenko/netutils-linux.svg?branch=master - :target: https://travis-ci.org/strizhechenko/netutils-linux .. |pypi| image:: https://badge.fury.io/py/netutils-linux.svg :target: https://badge.fury.io/py/netutils-linux .. |license| image:: https://img.shields.io/badge/License-MIT-yellow.svg?colorB=green @@ -14,7 +12,7 @@ netutils-linux .. |codeclimate| image:: https://img.shields.io/codeclimate/github/strizhechenko/netutils-linux.svg :target: https://codeclimate.com/github/strizhechenko/netutils-linux -|travis| |pypi| |license| |pyversions| |codeclimate| |issues| +|pypi| |license| |pyversions| |codeclimate| |issues| It's a useful utils to simplify Linux network troubleshooting and performance tuning, developed in order to help `Carbon Reductor`_ techsupport and automate the whole linux performance tuning process out of box (ok, except the best RSS layout detection with multiple network devices). These utils may be useful for datacenters and internet service providers with heavy network workload (you probably wouldn't see an effect at your desktop computer). It's now in production usage with 300+ deployment and save us a lot of time with hardware and software settings debugging. Inspired by `packagecloud's blog post`_. diff --git a/setup.py b/setup.py index aa0e9c3..3320a71 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ def read(*paths): setuptools.setup( name='netutils-linux', - version='2.7.11', + version='2.8.0', author='Oleg Strizhechenko', author_email='oleg.strizhechenko@gmail.com', license='MIT', From 7e856995a936e141cc2c37aa206ec6f17bc861ae Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Wed, 18 May 2022 19:42:00 +0500 Subject: [PATCH 22/26] README: Too old stat about production --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0f0712a..8d68f4c 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ netutils-linux |pypi| |license| |pyversions| |codeclimate| |issues| -It's a useful utils to simplify Linux network troubleshooting and performance tuning, developed in order to help `Carbon Reductor`_ techsupport and automate the whole linux performance tuning process out of box (ok, except the best RSS layout detection with multiple network devices). These utils may be useful for datacenters and internet service providers with heavy network workload (you probably wouldn't see an effect at your desktop computer). It's now in production usage with 300+ deployment and save us a lot of time with hardware and software settings debugging. Inspired by `packagecloud's blog post`_. +It's a useful utils to simplify Linux network troubleshooting and performance tuning, developed in order to help `Carbon Reductor`_ techsupport and automate the whole linux performance tuning process out of box (ok, except the best RSS layout detection with multiple network devices). These utils may be useful for datacenters and internet service providers with heavy network workload (you probably wouldn't see an effect at your desktop computer). It's now in production usage with 2000+ deployment and save us a lot of time with hardware and software settings debugging. Inspired by `packagecloud's blog post`_. .. _packagecloud's blog post: https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/ .. _Carbon Reductor: http://www.carbonsoft.ru/products/carbon-reductor-5/ From 708957e20a6340321754a6f041a4a3da2708576e Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Wed, 18 May 2022 19:46:47 +0500 Subject: [PATCH 23/26] Refactoring: removed unused imports --- netutils_linux_monitoring/irqtop.py | 1 - netutils_linux_monitoring/softirqs.py | 1 - netutils_linux_monitoring/softnet_stat.py | 1 - setup.py | 1 + 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/netutils_linux_monitoring/irqtop.py b/netutils_linux_monitoring/irqtop.py index 13ce082..c22c46c 100644 --- a/netutils_linux_monitoring/irqtop.py +++ b/netutils_linux_monitoring/irqtop.py @@ -6,7 +6,6 @@ from six.moves import xrange from netutils_linux_monitoring.base_top import BaseTop -from netutils_linux_monitoring.colors import Color from netutils_linux_monitoring.layout import make_table diff --git a/netutils_linux_monitoring/softirqs.py b/netutils_linux_monitoring/softirqs.py index ae97e2d..9b99922 100644 --- a/netutils_linux_monitoring/softirqs.py +++ b/netutils_linux_monitoring/softirqs.py @@ -2,7 +2,6 @@ from six import iteritems from netutils_linux_monitoring.base_top import BaseTop -from netutils_linux_monitoring.colors import Color from netutils_linux_monitoring.layout import make_table diff --git a/netutils_linux_monitoring/softnet_stat.py b/netutils_linux_monitoring/softnet_stat.py index 927daa6..56519cc 100644 --- a/netutils_linux_monitoring/softnet_stat.py +++ b/netutils_linux_monitoring/softnet_stat.py @@ -2,7 +2,6 @@ from random import randint from netutils_linux_monitoring.base_top import BaseTop -from netutils_linux_monitoring.colors import Color from netutils_linux_monitoring.layout import make_table diff --git a/setup.py b/setup.py index 3320a71..2dd61b2 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ def read(*paths): keywords='linux network performanse utils troubleshooting irq interrupts softirqs proc', description='Bunch of utils to simplify linux network troubleshooting and performance tuning.', long_description=(read('README.rst')), + long_description_content_type='text/x-rst', packages=setuptools.find_packages(exclude=['tests*']), scripts=[os.path.join('utils/', script) for script in os.listdir('utils/')], install_requires=['pyyaml', 'ipaddress', 'six', 'colorama', 'prettytable', 'argparse'], From db42e321ac76a4dd4fafb9cb281f1f41a2f627ca Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Sun, 28 Aug 2022 13:23:38 +0500 Subject: [PATCH 24/26] Refactoring(maximize-cpu-freq): using find -name instead of egrep & sort -V to get nice output --- utils/maximize-cpu-freq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/maximize-cpu-freq b/utils/maximize-cpu-freq index 18458de..f2e4e4a 100755 --- a/utils/maximize-cpu-freq +++ b/utils/maximize-cpu-freq @@ -52,7 +52,7 @@ maximize_freq() { main() { local cpu parse_params "$@" - for cpu in $(find /sys/devices/system/cpu/ -maxdepth 1 -type d | egrep 'cpu[0-9]+'); do + for cpu in $(find /sys/devices/system/cpu/ -maxdepth 1 -type d -name "cpu[0-9]*" | sort -V); do set_governor_performance "$cpu" maximize_freq "$cpu" done From 5244b23ab7f84671d2180809decdc7b6b9a38dec Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Sun, 28 May 2023 10:23:33 +0500 Subject: [PATCH 25/26] Project is frozen --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index 8d68f4c..fb87421 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,11 @@ netutils-linux |pypi| |license| |pyversions| |codeclimate| |issues| +Project is frozen +================= + +I will not receive any patches or do any changes for no reason. Feel free to fork, name it `netutils-linux-ng`, drop py2 support, etc. + It's a useful utils to simplify Linux network troubleshooting and performance tuning, developed in order to help `Carbon Reductor`_ techsupport and automate the whole linux performance tuning process out of box (ok, except the best RSS layout detection with multiple network devices). These utils may be useful for datacenters and internet service providers with heavy network workload (you probably wouldn't see an effect at your desktop computer). It's now in production usage with 2000+ deployment and save us a lot of time with hardware and software settings debugging. Inspired by `packagecloud's blog post`_. .. _packagecloud's blog post: https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/ From cdfc9e92f833837ed432b57a79fc21da6560be2f Mon Sep 17 00:00:00 2001 From: Oleg Strizhechenko Date: Sun, 28 May 2023 10:27:28 +0500 Subject: [PATCH 26/26] Project is frozen --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fb87421..ada66ab 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ netutils-linux Project is frozen ================= -I will not receive any patches or do any changes for no reason. Feel free to fork, name it `netutils-linux-ng`, drop py2 support, etc. +It's in licensing and intellectual property gray zone - previous employeer and me talked about it, but didn't fixed anything "on paper". I will not upgrade project, receive any patches, etc. Feel free to fork for own usage, drop py2 support, etc. It's a useful utils to simplify Linux network troubleshooting and performance tuning, developed in order to help `Carbon Reductor`_ techsupport and automate the whole linux performance tuning process out of box (ok, except the best RSS layout detection with multiple network devices). These utils may be useful for datacenters and internet service providers with heavy network workload (you probably wouldn't see an effect at your desktop computer). It's now in production usage with 2000+ deployment and save us a lot of time with hardware and software settings debugging. Inspired by `packagecloud's blog post`_.