mirror of
https://github.com/xzeldon/htop.git
synced 2025-07-15 05:24:36 +03:00
Compare commits
140 Commits
Author | SHA1 | Date | |
---|---|---|---|
c7413fd677 | |||
8f0475cd73 | |||
a155fd0f8b | |||
549543f8e4 | |||
10b541b5e4 | |||
73f08debe0 | |||
8b98d3effb | |||
7e66ee1d28 | |||
a7a6571d14 | |||
cde72dd0b0 | |||
cb61865bb9 | |||
c144bf9ae5 | |||
ae518e20b7 | |||
cdf3f3c50b | |||
1f2f4fe891 | |||
ec809b7f71 | |||
b83ce85d89 | |||
ee1bf2f917 | |||
afc4a9d657 | |||
3f3691886a | |||
99aa906bc5 | |||
df955c8991 | |||
6a7b3fdc7d | |||
72c56691ec | |||
4ccad46045 | |||
7039abe109 | |||
2b7504b522 | |||
8b927ba596 | |||
0ffd772d28 | |||
64fb7181ee | |||
ba4c67942c | |||
3f0c172a60 | |||
7c43e02591 | |||
c6f946edd2 | |||
a2ca7583a9 | |||
469ae7a0bd | |||
c4239335b9 | |||
23b56193d7 | |||
edf319e53d | |||
b6c0667eae | |||
3ba695293c | |||
6133cac721 | |||
da653f8148 | |||
d35db47c9a | |||
978a7c894f | |||
79a6f6cb5b | |||
deb05fe7c2 | |||
82d34deaf1 | |||
fa3e0d06c2 | |||
82dce5cf8e | |||
8d987864e4 | |||
58b42e4cac | |||
2477a5a018 | |||
1a403eb7eb | |||
a3a7958721 | |||
4aeb146ce8 | |||
8c99683b04 | |||
265a7b8a50 | |||
939685dff9 | |||
3e1a27a981 | |||
9512fd7930 | |||
4a664c0df8 | |||
d0d9f202c5 | |||
a0ad0697a8 | |||
a133ffd829 | |||
fde1243443 | |||
6eab39c0ab | |||
2c3a64ac9c | |||
442c1596f6 | |||
f782f821f7 | |||
5b78ad2d53 | |||
1ef8c0e12f | |||
6fcb1994c8 | |||
5bc988ad6d | |||
6e9a5e9e49 | |||
14f428a172 | |||
6388033e10 | |||
b45eaf2fe1 | |||
230dc9c3c1 | |||
d084a80023 | |||
5c8670717a | |||
bc08c7dc2a | |||
1e94b92226 | |||
63fafb4844 | |||
c85e5bbf5c | |||
c9e0bd2002 | |||
df1914f429 | |||
3cfdf66d9a | |||
fa9f260f63 | |||
1da78b5818 | |||
2ae1906479 | |||
0e58784224 | |||
4ef5e4296e | |||
ba3a1df806 | |||
b672e60886 | |||
cd6457ef88 | |||
31fe29c5a7 | |||
cc2547fcf0 | |||
72ba20fa5f | |||
ff4f44b22a | |||
a38f48481e | |||
61c9fe44a3 | |||
ff0ea41c86 | |||
43e9be5a8f | |||
d73783d6db | |||
89b7c4c9f9 | |||
78aefc2a99 | |||
697c502b7e | |||
171aa0faaa | |||
ddfacb8694 | |||
5beef3e737 | |||
27a18830d5 | |||
b98a4f8d56 | |||
55e073a455 | |||
915b558bbe | |||
550100327b | |||
9dc964bb42 | |||
ea4282784d | |||
1c3c149d20 | |||
42c0493021 | |||
1284ab4835 | |||
07496eafb0 | |||
e7a8d14cbd | |||
21cb1c4d59 | |||
e64269df2c | |||
d8dfbbd37c | |||
afa3fe4af1 | |||
2ef70ad7f6 | |||
2977414d54 | |||
696f79fe50 | |||
4374a267be | |||
3e70de64b3 | |||
3f9f52fd29 | |||
dfa62506b7 | |||
731b4003a2 | |||
30c5004cbb | |||
9d3a1d4981 | |||
60cfa2edce | |||
604744f68d | |||
13fe58f54a |
51
.github/workflows/build_release.yml
vendored
Normal file
51
.github/workflows/build_release.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
name: Build Source Release
|
||||
|
||||
# Trigger whenever a release is created
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: archive
|
||||
id: archive
|
||||
run: |
|
||||
VERSION=${{ github.event.release.tag_name }}
|
||||
PKGNAME="htop-$VERSION"
|
||||
SHASUM=$PKGNAME.tar.xz.sha256
|
||||
autoreconf -i
|
||||
mkdir -p /tmp/$PKGNAME
|
||||
mv * /tmp/$PKGNAME
|
||||
mv /tmp/$PKGNAME .
|
||||
TARBALL=$PKGNAME.tar.xz
|
||||
tar cJf $TARBALL $PKGNAME
|
||||
sha256sum $TARBALL > $SHASUM
|
||||
echo "::set-output name=tarball::$TARBALL"
|
||||
echo "::set-output name=shasum::$SHASUM"
|
||||
- name: upload tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ./${{ steps.archive.outputs.tarball }}
|
||||
asset_name: ${{ steps.archive.outputs.tarball }}
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: upload shasum
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ./${{ steps.archive.outputs.shasum }}
|
||||
asset_name: ${{ steps.archive.outputs.shasum }}
|
||||
asset_content_type: text/plain
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -113,6 +113,10 @@ jobs:
|
||||
build-ubuntu-latest-pcp:
|
||||
# Turns out 'ubuntu-latest' can be older than 20.04, we want PCP v5+
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
# Until Ubuntu catches up with pcp-5.2.3+:
|
||||
# pcp/Platform.c:309:45: warning: passing argument 2 of ‘pmLookupName’ from incompatible pointer type [-Wincompatible-pointer-types]
|
||||
CFLAGS: -Wno-error=incompatible-pointer-types
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
@ -120,9 +124,7 @@ jobs:
|
||||
- name: Bootstrap
|
||||
run: ./autogen.sh
|
||||
- name: Configure
|
||||
# Until Ubuntu catches up with pcp-5.2.3+, cannot use -werror due to:
|
||||
# passing argument 2 of ‘pmLookupName’ from incompatible pointer type
|
||||
run: ./configure --enable-pcp --enable-unicode
|
||||
run: ./configure --enable-werror --enable-pcp --enable-unicode
|
||||
- name: Build
|
||||
run: make -k
|
||||
|
||||
|
85
Action.c
85
Action.c
@ -58,7 +58,7 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess)
|
||||
header->pl->following = pid;
|
||||
unfollow = true;
|
||||
}
|
||||
ScreenManager_run(scr, &panelFocus, &ch);
|
||||
ScreenManager_run(scr, &panelFocus, &ch, NULL);
|
||||
if (unfollow) {
|
||||
header->pl->following = -1;
|
||||
}
|
||||
@ -85,7 +85,7 @@ Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess)
|
||||
static void Action_runSetup(State* st) {
|
||||
ScreenManager* scr = ScreenManager_new(st->header, st->settings, st, true);
|
||||
CategoriesPanel_new(scr, st->settings, st->header, st->pl);
|
||||
ScreenManager_run(scr, NULL, NULL);
|
||||
ScreenManager_run(scr, NULL, NULL, "Setup");
|
||||
ScreenManager_delete(scr);
|
||||
if (st->settings->changed) {
|
||||
Header_writeBackToSettings(st->header);
|
||||
@ -154,7 +154,7 @@ static bool collapseIntoParent(Panel* panel) {
|
||||
}
|
||||
|
||||
Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) {
|
||||
Settings_setSortKey(settings, sortKey);
|
||||
ScreenSettings_setSortKey(settings->ss, sortKey);
|
||||
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_UPDATE_PANELHDR | HTOP_KEEP_FOLLOWING;
|
||||
}
|
||||
|
||||
@ -164,8 +164,9 @@ static Htop_Reaction actionSetSortColumn(State* st) {
|
||||
Htop_Reaction reaction = HTOP_OK;
|
||||
Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Sort ", "Cancel "));
|
||||
Panel_setHeader(sortPanel, "Sort by");
|
||||
const ProcessField* fields = st->settings->fields;
|
||||
Hashtable* dynamicColumns = st->settings->dynamicColumns;
|
||||
const Settings* settings = st->settings;
|
||||
const ProcessField* fields = settings->ss->fields;
|
||||
Hashtable* dynamicColumns = settings->dynamicColumns;
|
||||
for (int i = 0; fields[i]; i++) {
|
||||
char* name = NULL;
|
||||
if (fields[i] >= LAST_PROCESSFIELD) {
|
||||
@ -177,7 +178,7 @@ static Htop_Reaction actionSetSortColumn(State* st) {
|
||||
name = String_trim(Process_fields[fields[i]].name);
|
||||
}
|
||||
Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i]));
|
||||
if (fields[i] == Settings_getActiveSortKey(st->settings))
|
||||
if (fields[i] == ScreenSettings_getActiveSortKey(settings->ss))
|
||||
Panel_setSelected(sortPanel, i);
|
||||
|
||||
free(name);
|
||||
@ -188,8 +189,7 @@ static Htop_Reaction actionSetSortColumn(State* st) {
|
||||
}
|
||||
Object_delete(sortPanel);
|
||||
|
||||
if (st->pauseProcessUpdate)
|
||||
ProcessList_sort(st->pl);
|
||||
st->pl->needsSort = true;
|
||||
|
||||
return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
|
||||
}
|
||||
@ -231,16 +231,18 @@ static Htop_Reaction actionToggleMergedCommand(State* st) {
|
||||
}
|
||||
|
||||
static Htop_Reaction actionToggleTreeView(State* st) {
|
||||
st->settings->treeView = !st->settings->treeView;
|
||||
ScreenSettings* ss = st->settings->ss;
|
||||
ss->treeView = !ss->treeView;
|
||||
|
||||
if (!st->settings->allBranchesCollapsed)
|
||||
if (!ss->allBranchesCollapsed)
|
||||
ProcessList_expandTree(st->pl);
|
||||
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;
|
||||
}
|
||||
|
||||
static Htop_Reaction actionExpandOrCollapseAllBranches(State* st) {
|
||||
st->settings->allBranchesCollapsed = !st->settings->allBranchesCollapsed;
|
||||
if (st->settings->allBranchesCollapsed)
|
||||
ScreenSettings* ss = st->settings->ss;
|
||||
ss->allBranchesCollapsed = !ss->allBranchesCollapsed;
|
||||
if (ss->allBranchesCollapsed)
|
||||
ProcessList_collapseAllBranches(st->pl);
|
||||
else
|
||||
ProcessList_expandTree(st->pl);
|
||||
@ -277,9 +279,8 @@ static Htop_Reaction actionLowerPriority(State* st) {
|
||||
}
|
||||
|
||||
static Htop_Reaction actionInvertSortOrder(State* st) {
|
||||
Settings_invertSortOrder(st->settings);
|
||||
if (st->pauseProcessUpdate)
|
||||
ProcessList_sort(st->pl);
|
||||
ScreenSettings_invertSortOrder(st->settings->ss);
|
||||
st->pl->needsSort = true;
|
||||
return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;
|
||||
}
|
||||
|
||||
@ -289,7 +290,7 @@ static Htop_Reaction actionExpandOrCollapse(State* st) {
|
||||
}
|
||||
|
||||
static Htop_Reaction actionCollapseIntoParent(State* st) {
|
||||
if (!st->settings->treeView) {
|
||||
if (!st->settings->ss->treeView) {
|
||||
return HTOP_OK;
|
||||
}
|
||||
bool changed = collapseIntoParent((Panel*)st->mainPanel);
|
||||
@ -297,7 +298,46 @@ static Htop_Reaction actionCollapseIntoParent(State* st) {
|
||||
}
|
||||
|
||||
static Htop_Reaction actionExpandCollapseOrSortColumn(State* st) {
|
||||
return st->settings->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st);
|
||||
return st->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st);
|
||||
}
|
||||
|
||||
static Htop_Reaction actionNextScreen(State* st) {
|
||||
Settings* settings = st->settings;
|
||||
settings->ssIndex++;
|
||||
if (settings->ssIndex == settings->nScreens) {
|
||||
settings->ssIndex = 0;
|
||||
}
|
||||
settings->ss = settings->screens[settings->ssIndex];
|
||||
return HTOP_REFRESH;
|
||||
}
|
||||
|
||||
static Htop_Reaction actionPrevScreen(State* st) {
|
||||
Settings* settings = st->settings;
|
||||
if (settings->ssIndex == 0) {
|
||||
settings->ssIndex = settings->nScreens - 1;
|
||||
} else {
|
||||
settings->ssIndex--;
|
||||
}
|
||||
settings->ss = settings->screens[settings->ssIndex];
|
||||
return HTOP_REFRESH;
|
||||
}
|
||||
|
||||
Htop_Reaction Action_setScreenTab(Settings* settings, int x) {
|
||||
int s = 2;
|
||||
for (unsigned int i = 0; i < settings->nScreens; i++) {
|
||||
if (x < s) {
|
||||
return 0;
|
||||
}
|
||||
const char* name = settings->screens[i]->name;
|
||||
int len = strlen(name);
|
||||
if (x <= s + len + 1) {
|
||||
settings->ssIndex = i;
|
||||
settings->ss = settings->screens[i];
|
||||
return HTOP_REFRESH;
|
||||
}
|
||||
s += len + 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Htop_Reaction actionQuit(ATTR_UNUSED State* st) {
|
||||
@ -344,9 +384,12 @@ static Htop_Reaction actionKill(State* st) {
|
||||
if (Settings_isReadonly())
|
||||
return HTOP_OK;
|
||||
|
||||
Panel* signalsPanel = SignalsPanel_new();
|
||||
static int preSelectedSignal = SIGNALSPANEL_INITSELECTEDSIGNAL;
|
||||
|
||||
Panel* signalsPanel = SignalsPanel_new(preSelectedSignal);
|
||||
const ListItem* sgn = (ListItem*) Action_pickFromVector(st, signalsPanel, 14, true);
|
||||
if (sgn && sgn->key != 0) {
|
||||
preSelectedSignal = sgn->key;
|
||||
Panel_setHeader((Panel*)st->mainPanel, "Sending...");
|
||||
Panel_draw((Panel*)st->mainPanel, false, true, true, State_hideFunctionBar(st));
|
||||
refresh();
|
||||
@ -459,6 +502,7 @@ static const struct {
|
||||
bool roInactive;
|
||||
const char* info;
|
||||
} helpLeft[] = {
|
||||
{ .key = " Tab: ", .roInactive = false, .info = "switch to next screen tab" },
|
||||
{ .key = " Arrows: ", .roInactive = false, .info = "scroll process list" },
|
||||
{ .key = " Digits: ", .roInactive = false, .info = "incremental PID search" },
|
||||
{ .key = " F3 /: ", .roInactive = false, .info = "incremental name search" },
|
||||
@ -483,6 +527,7 @@ static const struct {
|
||||
bool roInactive;
|
||||
const char* info;
|
||||
} helpRight[] = {
|
||||
{ .key = " S-Tab: ", .roInactive = false, .info = "switch to previous screen tab" },
|
||||
{ .key = " Space: ", .roInactive = false, .info = "tag process" },
|
||||
{ .key = " c: ", .roInactive = false, .info = "tag process and its children" },
|
||||
{ .key = " U: ", .roInactive = false, .info = "untag all processes" },
|
||||
@ -558,7 +603,7 @@ static Htop_Reaction actionHelp(State* st) {
|
||||
addattrstr(CRT_colors[BAR_BORDER], "[");
|
||||
addattrstr(CRT_colors[SWAP], "used");
|
||||
#ifdef HTOP_LINUX
|
||||
addattrstr(CRT_colors[BAR_SHADOW], "/");
|
||||
addstr("/");
|
||||
addattrstr(CRT_colors[SWAP_CACHE], "cache");
|
||||
addattrstr(CRT_colors[BAR_SHADOW], " used/total");
|
||||
#else
|
||||
@ -711,4 +756,6 @@ void Action_setBindings(Htop_Action* keys) {
|
||||
keys[KEY_F(10)] = actionQuit;
|
||||
keys[KEY_F(18)] = actionExpandCollapseOrSortColumn;
|
||||
keys[KEY_RECLICK] = actionExpandOrCollapse;
|
||||
keys[KEY_SHIFT_TAB] = actionPrevScreen;
|
||||
keys['\t'] = actionNextScreen;
|
||||
}
|
||||
|
2
Action.h
2
Action.h
@ -57,6 +57,8 @@ bool Action_setUserOnly(const char* userName, uid_t* userId);
|
||||
|
||||
Htop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey);
|
||||
|
||||
Htop_Reaction Action_setScreenTab(Settings* settings, int x);
|
||||
|
||||
Htop_Reaction Action_follow(State* st);
|
||||
|
||||
void Action_setBindings(Htop_Action* keys);
|
||||
|
53
CRT.c
53
CRT.c
@ -13,6 +13,7 @@ in the source distribution for its full text.
|
||||
#include <fcntl.h>
|
||||
#include <langinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -193,6 +194,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
|
||||
[CPU_SOFTIRQ] = ColorPair(Magenta, Black),
|
||||
[CPU_STEAL] = ColorPair(Cyan, Black),
|
||||
[CPU_GUEST] = ColorPair(Cyan, Black),
|
||||
[PANEL_EDIT] = ColorPair(White, Blue),
|
||||
[SCREENS_OTH_BORDER] = ColorPair(Blue, Blue),
|
||||
[SCREENS_OTH_TEXT] = ColorPair(Black, Blue),
|
||||
[SCREENS_CUR_BORDER] = ColorPair(Green, Green),
|
||||
[SCREENS_CUR_TEXT] = ColorPair(Black, Green),
|
||||
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Cyan, Black),
|
||||
[PRESSURE_STALL_SIXTY] = A_BOLD | ColorPair(Cyan, Black),
|
||||
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Black),
|
||||
@ -295,6 +301,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
|
||||
[CPU_SOFTIRQ] = A_BOLD,
|
||||
[CPU_STEAL] = A_DIM,
|
||||
[CPU_GUEST] = A_DIM,
|
||||
[PANEL_EDIT] = A_BOLD,
|
||||
[SCREENS_OTH_BORDER] = A_DIM,
|
||||
[SCREENS_OTH_TEXT] = A_DIM,
|
||||
[SCREENS_CUR_BORDER] = A_REVERSE,
|
||||
[SCREENS_CUR_TEXT] = A_REVERSE,
|
||||
[PRESSURE_STALL_THREEHUNDRED] = A_DIM,
|
||||
[PRESSURE_STALL_SIXTY] = A_NORMAL,
|
||||
[PRESSURE_STALL_TEN] = A_BOLD,
|
||||
@ -397,6 +408,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
|
||||
[CPU_SOFTIRQ] = ColorPair(Blue, White),
|
||||
[CPU_STEAL] = ColorPair(Cyan, White),
|
||||
[CPU_GUEST] = ColorPair(Cyan, White),
|
||||
[PANEL_EDIT] = ColorPair(White,Blue),
|
||||
[SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Black,White),
|
||||
[SCREENS_OTH_TEXT] = A_BOLD | ColorPair(Black,White),
|
||||
[SCREENS_CUR_BORDER] = ColorPair(Green,Green),
|
||||
[SCREENS_CUR_TEXT] = ColorPair(Black,Green),
|
||||
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, White),
|
||||
[PRESSURE_STALL_SIXTY] = ColorPair(Black, White),
|
||||
[PRESSURE_STALL_TEN] = ColorPair(Black, White),
|
||||
@ -499,6 +515,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
|
||||
[CPU_SOFTIRQ] = ColorPair(Blue, Black),
|
||||
[CPU_STEAL] = ColorPair(Black, Black),
|
||||
[CPU_GUEST] = ColorPair(Black, Black),
|
||||
[PANEL_EDIT] = ColorPair(White,Blue),
|
||||
[SCREENS_OTH_BORDER] = ColorPair(Blue,Black),
|
||||
[SCREENS_OTH_TEXT] = ColorPair(Blue,Black),
|
||||
[SCREENS_CUR_BORDER] = ColorPair(Green,Green),
|
||||
[SCREENS_CUR_TEXT] = ColorPair(Black,Green),
|
||||
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, Black),
|
||||
[PRESSURE_STALL_SIXTY] = ColorPair(Black, Black),
|
||||
[PRESSURE_STALL_TEN] = ColorPair(Black, Black),
|
||||
@ -601,6 +622,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
|
||||
[CPU_SOFTIRQ] = ColorPair(Black, Blue),
|
||||
[CPU_STEAL] = ColorPair(White, Blue),
|
||||
[CPU_GUEST] = ColorPair(White, Blue),
|
||||
[PANEL_EDIT] = ColorPair(White,Blue),
|
||||
[SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Yellow,Blue),
|
||||
[SCREENS_OTH_TEXT] = ColorPair(Cyan,Blue),
|
||||
[SCREENS_CUR_BORDER] = ColorPair(Cyan,Cyan),
|
||||
[SCREENS_CUR_TEXT] = ColorPair(Black,Cyan),
|
||||
[PRESSURE_STALL_THREEHUNDRED] = A_BOLD | ColorPair(Black, Blue),
|
||||
[PRESSURE_STALL_SIXTY] = A_NORMAL | ColorPair(White, Blue),
|
||||
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Blue),
|
||||
@ -701,6 +727,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
|
||||
[CPU_SOFTIRQ] = ColorPair(Blue, Black),
|
||||
[CPU_STEAL] = ColorPair(Cyan, Black),
|
||||
[CPU_GUEST] = ColorPair(Cyan, Black),
|
||||
[PANEL_EDIT] = ColorPair(White,Cyan),
|
||||
[SCREENS_OTH_BORDER] = ColorPair(White,Black),
|
||||
[SCREENS_OTH_TEXT] = ColorPair(Cyan,Black),
|
||||
[SCREENS_CUR_BORDER] = A_BOLD | ColorPair(White,Black),
|
||||
[SCREENS_CUR_TEXT] = A_BOLD | ColorPair(Green,Black),
|
||||
[PRESSURE_STALL_THREEHUNDRED] = ColorPair(Green, Black),
|
||||
[PRESSURE_STALL_SIXTY] = ColorPair(Green, Black),
|
||||
[PRESSURE_STALL_TEN] = A_BOLD | ColorPair(Green, Black),
|
||||
@ -725,8 +756,6 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {
|
||||
[COLORSCHEME_BROKENGRAY] = { 0 } // dynamically generated.
|
||||
};
|
||||
|
||||
int CRT_cursorX = 0;
|
||||
|
||||
int CRT_scrollHAmount = 5;
|
||||
|
||||
int CRT_scrollWheelVAmount = 10;
|
||||
@ -816,6 +845,16 @@ static void dumpStderr(void) {
|
||||
stderrRedirectNewFd = -1;
|
||||
}
|
||||
|
||||
void CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
fprintf(stderr, "[%s:%zu (%s)]: ", file, lineno, func);
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
#else /* !NDEBUG */
|
||||
|
||||
static void redirectStderr(void) {
|
||||
@ -841,6 +880,7 @@ static void CRT_installSignalHandlers(void) {
|
||||
sigaction (SIGSYS, &act, &old_sig_handler[SIGSYS]);
|
||||
sigaction (SIGABRT, &act, &old_sig_handler[SIGABRT]);
|
||||
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
signal(SIGINT, CRT_handleSIGTERM);
|
||||
signal(SIGTERM, CRT_handleSIGTERM);
|
||||
signal(SIGQUIT, CRT_handleSIGTERM);
|
||||
@ -915,6 +955,7 @@ IGNORE_WCASTQUAL_BEGIN
|
||||
define_key("\033[14~", KEY_F(4));
|
||||
define_key("\033[14;2~", KEY_F(15));
|
||||
define_key("\033[17;2~", KEY_F(18));
|
||||
define_key("\033[Z", KEY_SHIFT_TAB);
|
||||
char sequence[3] = "\033a";
|
||||
for (char c = 'a'; c <= 'z'; c++) {
|
||||
sequence[1] = c;
|
||||
@ -925,6 +966,9 @@ IGNORE_WCASTQUAL_END
|
||||
#undef define_key
|
||||
#endif
|
||||
}
|
||||
if (termType && (String_startsWith(termType, "rxvt"))) {
|
||||
define_key("\033[Z", KEY_SHIFT_TAB);
|
||||
}
|
||||
|
||||
CRT_installSignalHandlers();
|
||||
|
||||
@ -961,6 +1005,11 @@ IGNORE_WCASTQUAL_END
|
||||
}
|
||||
|
||||
void CRT_done() {
|
||||
attron(CRT_colors[RESET_COLOR]);
|
||||
mvhline(LINES - 1, 0, ' ', COLS);
|
||||
attroff(CRT_colors[RESET_COLOR]);
|
||||
refresh();
|
||||
|
||||
curs_set(1);
|
||||
endwin();
|
||||
|
||||
|
13
CRT.h
13
CRT.h
@ -120,6 +120,11 @@ typedef enum ColorElements_ {
|
||||
CPU_SOFTIRQ,
|
||||
CPU_STEAL,
|
||||
CPU_GUEST,
|
||||
PANEL_EDIT,
|
||||
SCREENS_OTH_BORDER,
|
||||
SCREENS_OTH_TEXT,
|
||||
SCREENS_CUR_BORDER,
|
||||
SCREENS_CUR_TEXT,
|
||||
PRESSURE_STALL_TEN,
|
||||
PRESSURE_STALL_SIXTY,
|
||||
PRESSURE_STALL_THREEHUNDRED,
|
||||
@ -145,11 +150,19 @@ typedef enum ColorElements_ {
|
||||
|
||||
void CRT_fatalError(const char* note) ATTR_NORETURN;
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define CRT_debug(...)
|
||||
#else
|
||||
void CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...) ATTR_FORMAT(printf, 4, 5);
|
||||
# define CRT_debug(...) CRT_debug_impl(__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
void CRT_handleSIGSEGV(int signal) ATTR_NORETURN;
|
||||
|
||||
#define KEY_WHEELUP KEY_F(30)
|
||||
#define KEY_WHEELDOWN KEY_F(31)
|
||||
#define KEY_RECLICK KEY_F(32)
|
||||
#define KEY_SHIFT_TAB KEY_F(33)
|
||||
#define KEY_ALT(x) (KEY_F(64 - 26) + ((x) - 'A'))
|
||||
|
||||
extern const char* CRT_degreeSign;
|
||||
|
@ -14,7 +14,6 @@ in the source distribution for its full text.
|
||||
#include "AvailableColumnsPanel.h"
|
||||
#include "AvailableMetersPanel.h"
|
||||
#include "ColorsPanel.h"
|
||||
#include "ColumnsPanel.h"
|
||||
#include "DisplayOptionsPanel.h"
|
||||
#include "FunctionBar.h"
|
||||
#include "Header.h"
|
||||
@ -25,6 +24,7 @@ in the source distribution for its full text.
|
||||
#include "MetersPanel.h"
|
||||
#include "Object.h"
|
||||
#include "ProvideCurses.h"
|
||||
#include "ScreensPanel.h"
|
||||
#include "Vector.h"
|
||||
#include "XUtils.h"
|
||||
|
||||
@ -69,9 +69,11 @@ static void CategoriesPanel_makeColorsPage(CategoriesPanel* this) {
|
||||
ScreenManager_add(this->scr, colors, -1);
|
||||
}
|
||||
|
||||
static void CategoriesPanel_makeColumnsPage(CategoriesPanel* this) {
|
||||
Panel* columns = (Panel*) ColumnsPanel_new(this->settings);
|
||||
static void CategoriesPanel_makeScreensPage(CategoriesPanel* this) {
|
||||
Panel* screens = (Panel*) ScreensPanel_new(this->settings);
|
||||
Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns;
|
||||
Panel* availableColumns = (Panel*) AvailableColumnsPanel_new(columns, this->settings->dynamicColumns);
|
||||
ScreenManager_add(this->scr, screens, 20);
|
||||
ScreenManager_add(this->scr, columns, 20);
|
||||
ScreenManager_add(this->scr, availableColumns, -1);
|
||||
}
|
||||
@ -91,7 +93,7 @@ static const CategoriesPanelPage categoriesPanelPages[] = {
|
||||
{ .name = "Display options", .ctor = CategoriesPanel_makeDisplayOptionsPage },
|
||||
{ .name = "Header layout", .ctor = CategoriesPanel_makeHeaderOptionsPage },
|
||||
{ .name = "Meters", .ctor = CategoriesPanel_makeMetersPage },
|
||||
{ .name = "Columns", .ctor = CategoriesPanel_makeColumnsPage },
|
||||
{ .name = "Screens", .ctor = CategoriesPanel_makeScreensPage },
|
||||
{ .name = "Colors", .ctor = CategoriesPanel_makeColorsPage },
|
||||
};
|
||||
|
||||
@ -157,7 +159,7 @@ CategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Settings* settings, Hea
|
||||
this->settings = settings;
|
||||
this->header = header;
|
||||
this->pl = pl;
|
||||
Panel_setHeader(super, "Setup");
|
||||
Panel_setHeader(super, "Categories");
|
||||
for (size_t i = 0; i < ARRAYSIZE(categoriesPanelPages); i++)
|
||||
Panel_add(super, (Object*) ListItem_new(categoriesPanelPages[i].name, 0));
|
||||
|
||||
|
49
ChangeLog
49
ChangeLog
@ -1,3 +1,52 @@
|
||||
What's new in version 3.2.0
|
||||
|
||||
* Support for displaying multiple tabs in the user interface
|
||||
* Allow multiple filter and search terms (logical OR, separate by "|")
|
||||
* Set correct default sorting direction (defaultSortDesc)
|
||||
* Improve performance for process lookup and update
|
||||
* Rework the IOMeters initial display
|
||||
* Removed duplicate sections on COMM and EXE
|
||||
* Highlight process UNINTERRUPTIBLE_WAIT state (D)
|
||||
* Show only integer value when CPU% more than 99.9%
|
||||
* Handle rounding ambiguity between 99.9 and 100.0%
|
||||
* No longer leaves empty the last column in header
|
||||
* Fix header layout and meters reset if a header column is empty
|
||||
* Fix PID and UID column widths off-by-one error
|
||||
* On Linux, read generic sysfs batteries
|
||||
* On Linux, do not collect LRS per thread (it is process-wide)
|
||||
* On Linux, dynamically adjust the SECATTR and CGROUP column widths
|
||||
* On Linux, fix a crash in LXD
|
||||
* On FreeBSD, add support for showing process emulation
|
||||
* On Darwin, lazily set process TTY name
|
||||
* Always set SIGCHLD to default handling
|
||||
* Avoid zombie processes on signal races
|
||||
* Ensure last line is cleared when SIGINT is received
|
||||
* Instead of SIGTERM, pre-select the last sent signal
|
||||
* Internal Hashtable performance and sizing improvements
|
||||
* Add heuristics for guessing LXC or Docker from /proc/1/mounts
|
||||
* Force elapsed time display to zero if process started in the future
|
||||
* Avoid extremely large year values when printing time
|
||||
* Fix division by zero when calculating IO rates
|
||||
* Fix out of boundary writes in XUtils
|
||||
* Fix custom thread name display issue
|
||||
* Use AC_CANONICAL_HOST, not AC_CANONICAL_TARGET in configure.ac
|
||||
* Support libunwind of LLVM
|
||||
|
||||
What's new in version 3.1.2
|
||||
|
||||
* Bugfix for crash when storing modified settings at exit
|
||||
* Generate xz-compressed source tarball (with configure) using github actions
|
||||
* Allow -u UID with numerical value as argument
|
||||
* Added documentation for obsolete/state libraries/program files highlighting
|
||||
* Some obsolete/stale library highlighting refinements
|
||||
* Column width issues resolved
|
||||
* Dynamic UID column sizing improved
|
||||
* Discard stale information from Disk and Network I/O meters
|
||||
* Refined Linux kernel thread detection
|
||||
* Reworked process state handling
|
||||
* New CCGROUP column showing abbreviated cgroup name
|
||||
* New OFFSET column in the list of open files screen
|
||||
|
||||
What's new in version 3.1.1
|
||||
|
||||
* Update license headers to explicitly say GPLv2+
|
||||
|
@ -52,7 +52,7 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) {
|
||||
HandlerResult result = IGNORED;
|
||||
int mark;
|
||||
|
||||
switch(ch) {
|
||||
switch (ch) {
|
||||
case 0x0a:
|
||||
case 0x0d:
|
||||
case KEY_ENTER:
|
||||
|
@ -138,20 +138,26 @@ static void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns)
|
||||
Panel_add(super, (Object*) ListItem_new(name, key));
|
||||
}
|
||||
|
||||
ColumnsPanel* ColumnsPanel_new(Settings* settings) {
|
||||
void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) {
|
||||
Panel* super = (Panel*) this;
|
||||
Panel_prune(super);
|
||||
for (const ProcessField* fields = ss->fields; *fields; fields++)
|
||||
ColumnsPanel_add(super, *fields, columns);
|
||||
this->ss = ss;
|
||||
}
|
||||
|
||||
ColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed) {
|
||||
ColumnsPanel* this = AllocThis(ColumnsPanel);
|
||||
Panel* super = (Panel*) this;
|
||||
FunctionBar* fuBar = FunctionBar_new(ColumnsFunctions, NULL, NULL);
|
||||
Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
|
||||
|
||||
this->settings = settings;
|
||||
this->ss = ss;
|
||||
this->changed = changed;
|
||||
this->moving = false;
|
||||
Panel_setHeader(super, "Active Columns");
|
||||
|
||||
Hashtable* dynamicColumns = settings->dynamicColumns;
|
||||
const ProcessField* fields = settings->fields;
|
||||
for (; *fields; fields++)
|
||||
ColumnsPanel_add(super, *fields, dynamicColumns);
|
||||
ColumnsPanel_fill(this, ss, columns);
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -159,14 +165,14 @@ ColumnsPanel* ColumnsPanel_new(Settings* settings) {
|
||||
void ColumnsPanel_update(Panel* super) {
|
||||
ColumnsPanel* this = (ColumnsPanel*) super;
|
||||
int size = Panel_size(super);
|
||||
this->settings->changed = true;
|
||||
this->settings->fields = xRealloc(this->settings->fields, sizeof(ProcessField) * (size + 1));
|
||||
this->settings->flags = 0;
|
||||
*(this->changed) = true;
|
||||
this->ss->fields = xRealloc(this->ss->fields, sizeof(ProcessField) * (size + 1));
|
||||
this->ss->flags = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
int key = ((ListItem*) Panel_get(super, i))->key;
|
||||
this->settings->fields[i] = key;
|
||||
this->ss->fields[i] = key;
|
||||
if (key < LAST_PROCESSFIELD)
|
||||
this->settings->flags |= Process_fields[key].flags;
|
||||
this->ss->flags |= Process_fields[key].flags;
|
||||
}
|
||||
this->settings->fields[size] = 0;
|
||||
this->ss->fields[size] = 0;
|
||||
}
|
||||
|
@ -15,14 +15,17 @@ in the source distribution for its full text.
|
||||
|
||||
typedef struct ColumnsPanel_ {
|
||||
Panel super;
|
||||
ScreenSettings* ss;
|
||||
bool* changed;
|
||||
|
||||
Settings* settings;
|
||||
bool moving;
|
||||
} ColumnsPanel;
|
||||
|
||||
extern const PanelClass ColumnsPanel_class;
|
||||
|
||||
ColumnsPanel* ColumnsPanel_new(Settings* settings);
|
||||
ColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed);
|
||||
|
||||
void ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns);
|
||||
|
||||
void ColumnsPanel_update(Panel* super);
|
||||
|
||||
|
117
CommandLine.c
117
CommandLine.c
@ -11,6 +11,7 @@ in the source distribution for its full text.
|
||||
#include "CommandLine.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <locale.h>
|
||||
#include <stdbool.h>
|
||||
@ -52,15 +53,17 @@ static void printHelpFlag(const char* name) {
|
||||
"-d --delay=DELAY Set the delay between updates, in tenths of seconds\n"
|
||||
"-F --filter=FILTER Show only the commands matching the given filter\n"
|
||||
"-h --help Print this help screen\n"
|
||||
"-H --highlight-changes[=DELAY] Highlight new and old processes\n"
|
||||
"-M --no-mouse Disable the mouse\n"
|
||||
"-p --pid=PID[,PID,PID...] Show only the given PIDs\n"
|
||||
"-H --highlight-changes[=DELAY] Highlight new and old processes\n", name);
|
||||
#ifdef HAVE_GETMOUSE
|
||||
printf("-M --no-mouse Disable the mouse\n");
|
||||
#endif
|
||||
printf("-p --pid=PID[,PID,PID...] Show only the given PIDs\n"
|
||||
" --readonly Disable all system and process changing features\n"
|
||||
"-s --sort-key=COLUMN Sort by COLUMN in list view (try --sort-key=help for a list)\n"
|
||||
"-t --tree Show the tree view (can be combined with -s)\n"
|
||||
"-u --user[=USERNAME] Show only processes for a given user (or $USER)\n"
|
||||
"-U --no-unicode Do not use unicode but plain ASCII\n"
|
||||
"-V --version Print version info\n", name);
|
||||
"-V --version Print version info\n");
|
||||
Platform_longOptionsUsage(name);
|
||||
printf("\n"
|
||||
"Long options may be passed with a single dash.\n\n"
|
||||
@ -85,9 +88,9 @@ typedef struct CommandLineSettings_ {
|
||||
bool readonly;
|
||||
} CommandLineSettings;
|
||||
|
||||
static CommandLineSettings parseArguments(const char* program, int argc, char** argv) {
|
||||
static CommandLineStatus parseArguments(const char* program, int argc, char** argv, CommandLineSettings* flags) {
|
||||
|
||||
CommandLineSettings flags = {
|
||||
*flags = (CommandLineSettings) {
|
||||
.pidMatchList = NULL,
|
||||
.commFilter = NULL,
|
||||
.userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2))
|
||||
@ -130,10 +133,10 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
printHelpFlag(program);
|
||||
exit(0);
|
||||
return STATUS_OK_EXIT;
|
||||
case 'V':
|
||||
printVersionFlag(program);
|
||||
exit(0);
|
||||
return STATUS_OK_EXIT;
|
||||
case 's':
|
||||
assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
|
||||
if (String_eq(optarg, "help")) {
|
||||
@ -142,29 +145,29 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
|
||||
const char* description = Process_fields[j].description;
|
||||
if (name) printf("%19s %s\n", name, description);
|
||||
}
|
||||
exit(0);
|
||||
return STATUS_OK_EXIT;
|
||||
}
|
||||
flags.sortKey = 0;
|
||||
flags->sortKey = 0;
|
||||
for (int j = 1; j < LAST_PROCESSFIELD; j++) {
|
||||
if (Process_fields[j].name == NULL)
|
||||
continue;
|
||||
if (String_eq(optarg, Process_fields[j].name)) {
|
||||
flags.sortKey = j;
|
||||
flags->sortKey = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flags.sortKey == 0) {
|
||||
if (flags->sortKey == 0) {
|
||||
fprintf(stderr, "Error: invalid column \"%s\".\n", optarg);
|
||||
exit(1);
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
if (sscanf(optarg, "%16d", &(flags.delay)) == 1) {
|
||||
if (flags.delay < 1) flags.delay = 1;
|
||||
if (flags.delay > 100) flags.delay = 100;
|
||||
if (sscanf(optarg, "%16d", &(flags->delay)) == 1) {
|
||||
if (flags->delay < 1) flags->delay = 1;
|
||||
if (flags->delay > 100) flags->delay = 100;
|
||||
} else {
|
||||
fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg);
|
||||
exit(1);
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
@ -176,26 +179,30 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
flags.userId = geteuid();
|
||||
} else if (!Action_setUserOnly(username, &(flags.userId))) {
|
||||
fprintf(stderr, "Error: invalid user \"%s\".\n", username);
|
||||
exit(1);
|
||||
flags->userId = geteuid();
|
||||
} else if (!Action_setUserOnly(username, &(flags->userId))) {
|
||||
for (const char *itr = username; *itr; ++itr)
|
||||
if (!isdigit((unsigned char)*itr)) {
|
||||
fprintf(stderr, "Error: invalid user \"%s\".\n", username);
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
flags->userId = atol(username);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'C':
|
||||
flags.useColors = false;
|
||||
flags->useColors = false;
|
||||
break;
|
||||
case 'M':
|
||||
#ifdef HAVE_GETMOUSE
|
||||
flags.enableMouse = false;
|
||||
flags->enableMouse = false;
|
||||
#endif
|
||||
break;
|
||||
case 'U':
|
||||
flags.allowUnicode = false;
|
||||
flags->allowUnicode = false;
|
||||
break;
|
||||
case 't':
|
||||
flags.treeView = true;
|
||||
flags->treeView = true;
|
||||
break;
|
||||
case 'p': {
|
||||
assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
|
||||
@ -203,14 +210,14 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
|
||||
char* saveptr;
|
||||
const char* pid = strtok_r(argCopy, ",", &saveptr);
|
||||
|
||||
if (!flags.pidMatchList) {
|
||||
flags.pidMatchList = Hashtable_new(8, false);
|
||||
if (!flags->pidMatchList) {
|
||||
flags->pidMatchList = Hashtable_new(8, false);
|
||||
}
|
||||
|
||||
while(pid) {
|
||||
unsigned int num_pid = atoi(pid);
|
||||
// deepcode ignore CastIntegerToAddress: we just want a non-NUll pointer here
|
||||
Hashtable_put(flags.pidMatchList, num_pid, (void *) 1);
|
||||
// deepcode ignore CastIntegerToAddress: we just want a non-NULL pointer here
|
||||
Hashtable_put(flags->pidMatchList, num_pid, (void *) 1);
|
||||
pid = strtok_r(NULL, ",", &saveptr);
|
||||
}
|
||||
free(argCopy);
|
||||
@ -219,7 +226,7 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
|
||||
}
|
||||
case 'F': {
|
||||
assert(optarg);
|
||||
free_and_xStrdup(&flags.commFilter, optarg);
|
||||
free_and_xStrdup(&flags->commFilter, optarg);
|
||||
break;
|
||||
}
|
||||
case 'H': {
|
||||
@ -229,28 +236,30 @@ static CommandLineSettings parseArguments(const char* program, int argc, char**
|
||||
delay = argv[optind++];
|
||||
}
|
||||
if (delay) {
|
||||
if (sscanf(delay, "%16d", &(flags.highlightDelaySecs)) == 1) {
|
||||
if (flags.highlightDelaySecs < 1)
|
||||
flags.highlightDelaySecs = 1;
|
||||
if (sscanf(delay, "%16d", &(flags->highlightDelaySecs)) == 1) {
|
||||
if (flags->highlightDelaySecs < 1)
|
||||
flags->highlightDelaySecs = 1;
|
||||
} else {
|
||||
fprintf(stderr, "Error: invalid highlight delay value \"%s\".\n", delay);
|
||||
exit(1);
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
}
|
||||
flags.highlightChanges = true;
|
||||
flags->highlightChanges = true;
|
||||
break;
|
||||
}
|
||||
case 128:
|
||||
flags.readonly = true;
|
||||
flags->readonly = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (Platform_getLongOption(opt, argc, argv) == false)
|
||||
exit(1);
|
||||
break;
|
||||
default: {
|
||||
CommandLineStatus status;
|
||||
if ((status = Platform_getLongOption(opt, argc, argv)) != STATUS_OK)
|
||||
return status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
static void CommandLine_delay(ProcessList* pl, unsigned long millisec) {
|
||||
@ -283,12 +292,17 @@ int CommandLine_run(const char* name, int argc, char** argv) {
|
||||
else
|
||||
setlocale(LC_CTYPE, "");
|
||||
|
||||
CommandLineSettings flags = parseArguments(name, argc, argv);
|
||||
CommandLineStatus status = STATUS_OK;
|
||||
CommandLineSettings flags = { 0 };
|
||||
|
||||
if ((status = parseArguments(name, argc, argv, &flags)) != STATUS_OK)
|
||||
return status != STATUS_OK_EXIT ? 1 : 0;
|
||||
|
||||
if (flags.readonly)
|
||||
Settings_enableReadonly();
|
||||
|
||||
Platform_init();
|
||||
if (!Platform_init())
|
||||
return 1;
|
||||
|
||||
Process_setupColumnWidths();
|
||||
|
||||
@ -316,7 +330,7 @@ int CommandLine_run(const char* name, int argc, char** argv) {
|
||||
settings->enableMouse = false;
|
||||
#endif
|
||||
if (flags.treeView)
|
||||
settings->treeView = true;
|
||||
settings->ss->treeView = true;
|
||||
if (flags.highlightChanges)
|
||||
settings->highlightChanges = true;
|
||||
if (flags.highlightDelaySecs != -1)
|
||||
@ -325,9 +339,9 @@ int CommandLine_run(const char* name, int argc, char** argv) {
|
||||
// -t -s <key> means "tree sorted by key"
|
||||
// -s <key> means "list sorted by key" (previous existing behavior)
|
||||
if (!flags.treeView) {
|
||||
settings->treeView = false;
|
||||
settings->ss->treeView = false;
|
||||
}
|
||||
Settings_setSortKey(settings, flags.sortKey);
|
||||
ScreenSettings_setSortKey(settings->ss, flags.sortKey);
|
||||
}
|
||||
|
||||
CRT_init(settings, flags.allowUnicode);
|
||||
@ -335,7 +349,7 @@ int CommandLine_run(const char* name, int argc, char** argv) {
|
||||
MainPanel* panel = MainPanel_new();
|
||||
ProcessList_setPanel(pl, (Panel*) panel);
|
||||
|
||||
MainPanel_updateTreeFunctions(panel, settings->treeView);
|
||||
MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter);
|
||||
|
||||
State state = {
|
||||
.settings = settings,
|
||||
@ -358,15 +372,10 @@ int CommandLine_run(const char* name, int argc, char** argv) {
|
||||
CommandLine_delay(pl, 75);
|
||||
ProcessList_scan(pl, false);
|
||||
|
||||
if (settings->allBranchesCollapsed)
|
||||
if (settings->ss->allBranchesCollapsed)
|
||||
ProcessList_collapseAllBranches(pl);
|
||||
|
||||
ScreenManager_run(scr, NULL, NULL);
|
||||
|
||||
attron(CRT_colors[RESET_COLOR]);
|
||||
mvhline(LINES - 1, 0, ' ', COLS);
|
||||
attroff(CRT_colors[RESET_COLOR]);
|
||||
refresh();
|
||||
ScreenManager_run(scr, NULL, NULL, NULL);
|
||||
|
||||
Platform_done();
|
||||
|
||||
|
@ -8,6 +8,11 @@ Released under the GNU GPLv2+, see the COPYING file
|
||||
in the source distribution for its full text.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
STATUS_OK,
|
||||
STATUS_ERROR_EXIT,
|
||||
STATUS_OK_EXIT
|
||||
} CommandLineStatus;
|
||||
|
||||
int CommandLine_run(const char* name, int argc, char** argv);
|
||||
|
||||
|
2
Compat.c
2
Compat.c
@ -44,7 +44,7 @@ int Compat_faccessat(int dirfd,
|
||||
|
||||
// Fallback to stat(2)/lstat(2) depending on flags
|
||||
struct stat statinfo;
|
||||
if(flags) {
|
||||
if (flags) {
|
||||
ret = lstat(pathname, &statinfo);
|
||||
} else {
|
||||
ret = stat(pathname, &statinfo);
|
||||
|
@ -12,6 +12,7 @@ in the source distribution for its full text.
|
||||
|
||||
#include "CRT.h"
|
||||
#include "Macros.h"
|
||||
#include "Meter.h"
|
||||
#include "Object.h"
|
||||
#include "Platform.h"
|
||||
#include "ProcessList.h"
|
||||
@ -25,7 +26,7 @@ static const int DiskIOMeter_attributes[] = {
|
||||
METER_VALUE_IOWRITE,
|
||||
};
|
||||
|
||||
static bool hasData = false;
|
||||
static MeterRateStatus status = RATESTATUS_INIT;
|
||||
static uint32_t cached_read_diff;
|
||||
static uint32_t cached_write_diff;
|
||||
static double cached_utilisation_diff;
|
||||
@ -36,20 +37,27 @@ static void DiskIOMeter_updateValues(Meter* this) {
|
||||
static uint64_t cached_last_update;
|
||||
uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
|
||||
|
||||
/* update only every 500ms */
|
||||
/* update only every 500ms to have a sane span for rate calculation */
|
||||
if (passedTimeInMs > 500) {
|
||||
static uint64_t cached_read_total;
|
||||
static uint64_t cached_write_total;
|
||||
static uint64_t cached_msTimeSpend_total;
|
||||
uint64_t diff;
|
||||
|
||||
DiskIOData data;
|
||||
if (!Platform_getDiskIO(&data)) {
|
||||
status = RATESTATUS_NODATA;
|
||||
} else if (cached_last_update == 0) {
|
||||
status = RATESTATUS_INIT;
|
||||
} else if (passedTimeInMs > 30000) {
|
||||
status = RATESTATUS_STALE;
|
||||
} else {
|
||||
status = RATESTATUS_DATA;
|
||||
}
|
||||
|
||||
cached_last_update = pl->realtimeMs;
|
||||
|
||||
DiskIOData data;
|
||||
|
||||
hasData = Platform_getDiskIO(&data);
|
||||
if (!hasData) {
|
||||
this->values[0] = 0;
|
||||
if (status == RATESTATUS_NODATA) {
|
||||
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
|
||||
return;
|
||||
}
|
||||
@ -81,6 +89,15 @@ static void DiskIOMeter_updateValues(Meter* this) {
|
||||
cached_msTimeSpend_total = data.totalMsTimeSpend;
|
||||
}
|
||||
|
||||
if (status == RATESTATUS_INIT) {
|
||||
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init");
|
||||
return;
|
||||
}
|
||||
if (status == RATESTATUS_STALE) {
|
||||
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale");
|
||||
return;
|
||||
}
|
||||
|
||||
this->values[0] = cached_utilisation_diff;
|
||||
this->total = MAXIMUM(this->values[0], 100.0); /* fix total after (initial) spike */
|
||||
|
||||
@ -91,9 +108,18 @@ static void DiskIOMeter_updateValues(Meter* this) {
|
||||
}
|
||||
|
||||
static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
|
||||
if (!hasData) {
|
||||
switch (status) {
|
||||
case RATESTATUS_NODATA:
|
||||
RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
|
||||
return;
|
||||
case RATESTATUS_INIT:
|
||||
RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
|
||||
return;
|
||||
case RATESTATUS_STALE:
|
||||
RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
|
||||
return;
|
||||
case RATESTATUS_DATA:
|
||||
break;
|
||||
}
|
||||
|
||||
char buffer[16];
|
||||
|
@ -97,9 +97,10 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
|
||||
this->scr = scr;
|
||||
|
||||
Panel_setHeader(super, "Display options");
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Tree view", &(settings->treeView)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->treeViewAlwaysByPID)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->allBranchesCollapsed)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Tree view (for the current Screen tab)", &(settings->ss->treeView)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is always sorted by PID (htop 2 behavior)", &(settings->ss->treeViewAlwaysByPID)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("- Tree view is collapsed by default", &(settings->ss->allBranchesCollapsed)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Show tabs for screens", &(settings->screenTabs)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Shadow other users' processes", &(settings->shadowOtherUsers)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Hide kernel threads", &(settings->hideKernelThreads)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Hide userland process threads", &(settings->hideUserlandThreads)));
|
||||
@ -107,7 +108,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager*
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Show custom thread names", &(settings->showThreadNames)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Show program path", &(settings->showProgramPath)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Highlight program \"basename\"", &(settings->highlightBaseName)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Highlight out-dated/removed programs", &(settings->highlightDeletedExe)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Highlight out-dated/removed programs (red) / libraries (yellow)", &(settings->highlightDeletedExe)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("Merge exe, comm and cmdline in Command", &(settings->showMergedCommand)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("- Try to find comm in cmdline (when Command is merged)", &(settings->findCommInCmdline)));
|
||||
Panel_add(super, (Object*) CheckItem_newByRef("- Try to strip exe from cmdline (when Command is merged)", &(settings->stripExeFromCmdline)));
|
||||
|
@ -88,11 +88,12 @@ void FunctionBar_setLabel(FunctionBar* this, int event, const char* text) {
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionBar_draw(const FunctionBar* this) {
|
||||
FunctionBar_drawExtra(this, NULL, -1, false);
|
||||
int FunctionBar_draw(const FunctionBar* this) {
|
||||
return FunctionBar_drawExtra(this, NULL, -1, false);
|
||||
}
|
||||
|
||||
void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor) {
|
||||
int FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor) {
|
||||
int cursorX = 0;
|
||||
attrset(CRT_colors[FUNCTION_BAR]);
|
||||
mvhline(LINES - 1, 0, ' ', COLS);
|
||||
int x = 0;
|
||||
@ -113,18 +114,20 @@ void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr
|
||||
}
|
||||
mvaddstr(LINES - 1, x, buffer);
|
||||
x += strlen(buffer);
|
||||
cursorX = x;
|
||||
}
|
||||
|
||||
attrset(CRT_colors[RESET_COLOR]);
|
||||
|
||||
if (setCursor) {
|
||||
CRT_cursorX = x;
|
||||
curs_set(1);
|
||||
} else {
|
||||
curs_set(0);
|
||||
}
|
||||
|
||||
currentLen = x;
|
||||
|
||||
return cursorX;
|
||||
}
|
||||
|
||||
void FunctionBar_append(const char* buffer, int attr) {
|
||||
|
@ -29,9 +29,9 @@ void FunctionBar_delete(FunctionBar* this);
|
||||
|
||||
void FunctionBar_setLabel(FunctionBar* this, int event, const char* text);
|
||||
|
||||
void FunctionBar_draw(const FunctionBar* this);
|
||||
int FunctionBar_draw(const FunctionBar* this);
|
||||
|
||||
void FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor);
|
||||
int FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor);
|
||||
|
||||
void FunctionBar_append(const char* buffer, int attr);
|
||||
|
||||
|
10
Hashtable.c
10
Hashtable.c
@ -90,7 +90,7 @@ size_t Hashtable_count(const Hashtable* this) {
|
||||
|
||||
/* https://oeis.org/A014234 */
|
||||
static const uint64_t OEISprimes[] = {
|
||||
2, 3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191,
|
||||
7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191,
|
||||
16381, 32749, 65521, 131071, 262139, 524287, 1048573,
|
||||
2097143, 4194301, 8388593, 16777213, 33554393,
|
||||
67108859, 134217689, 268435399, 536870909, 1073741789,
|
||||
@ -191,10 +191,14 @@ void Hashtable_setSize(Hashtable* this, size_t size) {
|
||||
if (size <= this->items)
|
||||
return;
|
||||
|
||||
size_t newSize = nextPrime(size);
|
||||
if (newSize == this->size)
|
||||
return;
|
||||
|
||||
HashtableItem* oldBuckets = this->buckets;
|
||||
size_t oldSize = this->size;
|
||||
|
||||
this->size = nextPrime(size);
|
||||
this->size = newSize;
|
||||
this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem));
|
||||
this->items = 0;
|
||||
|
||||
@ -282,7 +286,7 @@ void* Hashtable_remove(Hashtable* this, ht_key_t key) {
|
||||
|
||||
/* shrink on load-factor < 0.125 */
|
||||
if (8 * this->items < this->size)
|
||||
Hashtable_setSize(this, this->size / 2);
|
||||
Hashtable_setSize(this, this->size / 3); /* account for nextPrime rounding up */
|
||||
|
||||
return res;
|
||||
}
|
||||
|
10
Header.c
10
Header.c
@ -145,7 +145,7 @@ void Header_writeBackToSettings(const Header* this) {
|
||||
const Vector* vec = this->columns[col];
|
||||
int len = Vector_size(vec);
|
||||
|
||||
colSettings->names = len ? xCalloc(len, sizeof(char*)) : NULL;
|
||||
colSettings->names = len ? xCalloc(len + 1, sizeof(char*)) : NULL;
|
||||
colSettings->modes = len ? xCalloc(len, sizeof(int)) : NULL;
|
||||
colSettings->len = len;
|
||||
|
||||
@ -194,7 +194,8 @@ void Header_draw(const Header* this) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
mvhline(y, 0, ' ', COLS);
|
||||
}
|
||||
const int width = COLS - pad;
|
||||
const int numCols = HeaderLayout_getColumns(this->headerLayout);
|
||||
const int width = COLS - 2 * pad - (numCols - 1);
|
||||
int x = pad;
|
||||
float roundingLoss = 0.0F;
|
||||
|
||||
@ -217,6 +218,7 @@ void Header_draw(const Header* this) {
|
||||
except for multi column meters. */
|
||||
if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) {
|
||||
for (int j = 1; j < meter->columnWidthCount; j++) {
|
||||
actualWidth++; /* separator column */
|
||||
actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F;
|
||||
}
|
||||
}
|
||||
@ -227,6 +229,7 @@ void Header_draw(const Header* this) {
|
||||
}
|
||||
|
||||
x += floorf(colWidth);
|
||||
x++; /* separator column */
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,6 +286,9 @@ int Header_calculateHeight(Header* this) {
|
||||
}
|
||||
maxHeight = MAXIMUM(maxHeight, height);
|
||||
}
|
||||
if (this->settings->screenTabs) {
|
||||
maxHeight++;
|
||||
}
|
||||
this->height = maxHeight;
|
||||
this->pad = pad;
|
||||
return maxHeight;
|
||||
|
@ -18,6 +18,7 @@ in the source distribution for its full text.
|
||||
|
||||
|
||||
typedef enum HeaderLayout_ {
|
||||
HF_INVALID = -1,
|
||||
HF_TWO_50_50,
|
||||
HF_TWO_33_67,
|
||||
HF_TWO_67_33,
|
||||
|
@ -35,7 +35,7 @@ static HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) {
|
||||
HandlerResult result = IGNORED;
|
||||
int mark;
|
||||
|
||||
switch(ch) {
|
||||
switch (ch) {
|
||||
case 0x0a:
|
||||
case 0x0d:
|
||||
case KEY_ENTER:
|
||||
|
41
IncSet.c
41
IncSet.c
@ -85,7 +85,7 @@ static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) {
|
||||
const char* incFilter = this->modes[INC_FILTER].buffer;
|
||||
for (int i = 0; i < Vector_size(lines); i++) {
|
||||
ListItem* line = (ListItem*)Vector_get(lines, i);
|
||||
if (String_contains_i(line->value, incFilter)) {
|
||||
if (String_contains_i(line->value, incFilter, true)) {
|
||||
Panel_add(panel, (Object*)line);
|
||||
if (selected == (Object*)line) {
|
||||
Panel_setSelected(panel, n);
|
||||
@ -105,10 +105,10 @@ static void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool search(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue) {
|
||||
static bool search(const IncSet* this, Panel* panel, IncMode_GetPanelValue getPanelValue) {
|
||||
int size = Panel_size(panel);
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (String_contains_i(getPanelValue(panel, i), mode->buffer)) {
|
||||
if (String_contains_i(getPanelValue(panel, i), this->active->buffer, true)) {
|
||||
Panel_setSelected(panel, i);
|
||||
return true;
|
||||
}
|
||||
@ -117,6 +117,21 @@ static bool search(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getP
|
||||
return false;
|
||||
}
|
||||
|
||||
void IncSet_activate(IncSet* this, IncType type, Panel* panel) {
|
||||
this->active = &(this->modes[type]);
|
||||
panel->currentBar = this->active->bar;
|
||||
panel->cursorOn = true;
|
||||
this->panel = panel;
|
||||
IncSet_drawBar(this, CRT_colors[FUNCTION_BAR]);
|
||||
}
|
||||
|
||||
static void IncSet_deactivate(IncSet* this, Panel* panel) {
|
||||
this->active = NULL;
|
||||
Panel_setDefaultBar(panel);
|
||||
panel->cursorOn = false;
|
||||
FunctionBar_draw(this->defaultBar);
|
||||
}
|
||||
|
||||
static bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue, int step) {
|
||||
int size = Panel_size(panel);
|
||||
int here = Panel_getSelectedIndex(panel);
|
||||
@ -133,7 +148,7 @@ static bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValu
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String_contains_i(getPanelValue(panel, i), mode->buffer)) {
|
||||
if (String_contains_i(getPanelValue(panel, i), mode->buffer, true)) {
|
||||
Panel_setSelected(panel, i);
|
||||
return true;
|
||||
}
|
||||
@ -194,12 +209,11 @@ bool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue
|
||||
IncMode_reset(mode);
|
||||
}
|
||||
}
|
||||
this->active = NULL;
|
||||
Panel_setDefaultBar(panel);
|
||||
IncSet_deactivate(this, panel);
|
||||
doSearch = false;
|
||||
}
|
||||
if (doSearch) {
|
||||
this->found = search(mode, panel, getPanelValue);
|
||||
this->found = search(this, panel, getPanelValue);
|
||||
}
|
||||
if (filterChanged && lines) {
|
||||
updateWeakPanel(this, panel, lines);
|
||||
@ -212,14 +226,13 @@ const char* IncSet_getListItemValue(Panel* panel, int i) {
|
||||
return l ? l->value : "";
|
||||
}
|
||||
|
||||
void IncSet_activate(IncSet* this, IncType type, Panel* panel) {
|
||||
this->active = &(this->modes[type]);
|
||||
panel->currentBar = this->active->bar;
|
||||
}
|
||||
|
||||
void IncSet_drawBar(const IncSet* this) {
|
||||
void IncSet_drawBar(const IncSet* this, int attr) {
|
||||
if (this->active) {
|
||||
FunctionBar_drawExtra(this->active->bar, this->active->buffer, (this->active->isFilter || this->found) ? -1 : CRT_colors[FAILED_SEARCH], true);
|
||||
if (!this->active->isFilter && !this->found)
|
||||
attr = CRT_colors[FAILED_SEARCH];
|
||||
int cursorX = FunctionBar_drawExtra(this->active->bar, this->active->buffer, attr, true);
|
||||
this->panel->cursorY = LINES - 1;
|
||||
this->panel->cursorX = cursorX;
|
||||
} else {
|
||||
FunctionBar_draw(this->defaultBar);
|
||||
}
|
||||
|
3
IncSet.h
3
IncSet.h
@ -32,6 +32,7 @@ typedef struct IncMode_ {
|
||||
typedef struct IncSet_ {
|
||||
IncMode modes[2];
|
||||
IncMode* active;
|
||||
Panel* panel;
|
||||
FunctionBar* defaultBar;
|
||||
bool filtering;
|
||||
bool found;
|
||||
@ -57,7 +58,7 @@ const char* IncSet_getListItemValue(Panel* panel, int i);
|
||||
|
||||
void IncSet_activate(IncSet* this, IncType type, Panel* panel);
|
||||
|
||||
void IncSet_drawBar(const IncSet* this);
|
||||
void IncSet_drawBar(const IncSet* this, int attr);
|
||||
|
||||
int IncSet_synthesizeEvent(IncSet* this, int x);
|
||||
|
||||
|
18
InfoScreen.c
18
InfoScreen.c
@ -57,13 +57,13 @@ void InfoScreen_drawTitled(InfoScreen* this, const char* fmt, ...) {
|
||||
attrset(CRT_colors[DEFAULT_COLOR]);
|
||||
Panel_draw(this->display, true, true, true, false);
|
||||
|
||||
IncSet_drawBar(this->inc);
|
||||
IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
|
||||
}
|
||||
|
||||
void InfoScreen_addLine(InfoScreen* this, const char* line) {
|
||||
Vector_add(this->lines, (Object*) ListItem_new(line, 0));
|
||||
const char* incFilter = IncSet_filter(this->inc);
|
||||
if (!incFilter || String_contains_i(line, incFilter)) {
|
||||
if (!incFilter || String_contains_i(line, incFilter, true)) {
|
||||
Panel_add(this->display, Vector_get(this->lines, Vector_size(this->lines) - 1));
|
||||
}
|
||||
}
|
||||
@ -72,7 +72,7 @@ void InfoScreen_appendLine(InfoScreen* this, const char* line) {
|
||||
ListItem* last = (ListItem*)Vector_get(this->lines, Vector_size(this->lines) - 1);
|
||||
ListItem_append(last, line);
|
||||
const char* incFilter = IncSet_filter(this->inc);
|
||||
if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter)) {
|
||||
if (incFilter && Panel_get(this->display, Panel_size(this->display) - 1) != (Object*)last && String_contains_i(line, incFilter, true)) {
|
||||
Panel_add(this->display, (Object*)last);
|
||||
}
|
||||
}
|
||||
@ -89,15 +89,9 @@ void InfoScreen_run(InfoScreen* this) {
|
||||
while (looping) {
|
||||
|
||||
Panel_draw(panel, false, true, true, false);
|
||||
IncSet_drawBar(this->inc);
|
||||
IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
|
||||
|
||||
if (this->inc->active) {
|
||||
(void) move(LINES - 1, CRT_cursorX);
|
||||
}
|
||||
#ifdef HAVE_SET_ESCDELAY
|
||||
set_escdelay(25);
|
||||
#endif
|
||||
int ch = getch();
|
||||
int ch = Panel_getCh(panel);
|
||||
|
||||
if (ch == ERR) {
|
||||
if (As_InfoScreen(this)->onErr) {
|
||||
@ -135,7 +129,7 @@ void InfoScreen_run(InfoScreen* this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(ch) {
|
||||
switch (ch) {
|
||||
case ERR:
|
||||
continue;
|
||||
case KEY_F(3):
|
||||
|
14
ListItem.c
14
ListItem.c
@ -18,13 +18,13 @@ in the source distribution for its full text.
|
||||
#include "XUtils.h"
|
||||
|
||||
|
||||
static void ListItem_delete(Object* cast) {
|
||||
void ListItem_delete(Object* cast) {
|
||||
ListItem* this = (ListItem*)cast;
|
||||
free(this->value);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static void ListItem_display(const Object* cast, RichString* out) {
|
||||
void ListItem_display(const Object* cast, RichString* out) {
|
||||
const ListItem* const this = (const ListItem*)cast;
|
||||
assert (this != NULL);
|
||||
|
||||
@ -38,11 +38,15 @@ static void ListItem_display(const Object* cast, RichString* out) {
|
||||
RichString_appendWide(out, CRT_colors[DEFAULT_COLOR], this->value);
|
||||
}
|
||||
|
||||
ListItem* ListItem_new(const char* value, int key) {
|
||||
ListItem* this = AllocThis(ListItem);
|
||||
void ListItem_init(ListItem* this, const char* value, int key) {
|
||||
this->value = xStrdup(value);
|
||||
this->key = key;
|
||||
this->moving = false;
|
||||
}
|
||||
|
||||
ListItem* ListItem_new(const char* value, int key) {
|
||||
ListItem* this = AllocThis(ListItem);
|
||||
ListItem_init(this, value, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -55,7 +59,7 @@ void ListItem_append(ListItem* this, const char* text) {
|
||||
this->value[newLen] = '\0';
|
||||
}
|
||||
|
||||
static int ListItem_compare(const void* cast1, const void* cast2) {
|
||||
int ListItem_compare(const void* cast1, const void* cast2) {
|
||||
const ListItem* obj1 = (const ListItem*) cast1;
|
||||
const ListItem* obj2 = (const ListItem*) cast2;
|
||||
return strcmp(obj1->value, obj2->value);
|
||||
|
@ -21,10 +21,18 @@ typedef struct ListItem_ {
|
||||
|
||||
extern const ObjectClass ListItem_class;
|
||||
|
||||
void ListItem_delete(Object* cast);
|
||||
|
||||
void ListItem_display(const Object* cast, RichString* out);
|
||||
|
||||
void ListItem_init(ListItem* this, const char* value, int key);
|
||||
|
||||
ListItem* ListItem_new(const char* value, int key);
|
||||
|
||||
void ListItem_append(ListItem* this, const char* text);
|
||||
|
||||
int ListItem_compare(const void* cast1, const void* cast2);
|
||||
|
||||
static inline const char* ListItem_getRef(const ListItem* this) {
|
||||
return this->value;
|
||||
}
|
||||
|
27
MainPanel.c
27
MainPanel.c
@ -24,9 +24,10 @@ in the source distribution for its full text.
|
||||
static const char* const MainFunctions[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ", "Quit ", NULL};
|
||||
static const char* const MainFunctions_ro[] = {"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", " ", " ", " ", "Quit ", NULL};
|
||||
|
||||
void MainPanel_updateTreeFunctions(MainPanel* this, bool mode) {
|
||||
void MainPanel_updateLabels(MainPanel* this, bool list, bool filter) {
|
||||
FunctionBar* bar = MainPanel_getFunctionBar(this);
|
||||
FunctionBar_setLabel(bar, KEY_F(5), mode ? "List " : "Tree ");
|
||||
FunctionBar_setLabel(bar, KEY_F(5), list ? "List " : "Tree ");
|
||||
FunctionBar_setLabel(bar, KEY_F(4), filter ? "FILTER" : "Filter");
|
||||
}
|
||||
|
||||
static void MainPanel_pidSearch(MainPanel* this, int ch) {
|
||||
@ -71,23 +72,29 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
|
||||
if (needReset)
|
||||
this->state->hideProcessSelection = false;
|
||||
|
||||
Settings* settings = this->state->settings;
|
||||
ScreenSettings* ss = settings->ss;
|
||||
|
||||
if (EVENT_IS_HEADER_CLICK(ch)) {
|
||||
int x = EVENT_HEADER_CLICK_GET_X(ch);
|
||||
const ProcessList* pl = this->state->pl;
|
||||
Settings* settings = this->state->settings;
|
||||
int hx = super->scrollH + x + 1;
|
||||
ProcessField field = ProcessList_keyAt(pl, hx);
|
||||
if (settings->treeView && settings->treeViewAlwaysByPID) {
|
||||
settings->treeView = false;
|
||||
settings->direction = 1;
|
||||
if (ss->treeView && ss->treeViewAlwaysByPID) {
|
||||
ss->treeView = false;
|
||||
ss->direction = 1;
|
||||
reaction |= Action_setSortKey(settings, field);
|
||||
} else if (field == Settings_getActiveSortKey(settings)) {
|
||||
Settings_invertSortOrder(settings);
|
||||
} else if (field == ScreenSettings_getActiveSortKey(ss)) {
|
||||
ScreenSettings_invertSortOrder(ss);
|
||||
} else {
|
||||
reaction |= Action_setSortKey(settings, field);
|
||||
}
|
||||
reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_SAVE_SETTINGS;
|
||||
result = HANDLED;
|
||||
} else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) {
|
||||
int x = EVENT_SCREEN_TAB_GET_X(ch);
|
||||
reaction |= Action_setScreenTab(settings, x);
|
||||
result = HANDLED;
|
||||
} else if (ch != ERR && this->inc->active) {
|
||||
bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL);
|
||||
if (filterChanged) {
|
||||
@ -116,7 +123,7 @@ static HandlerResult MainPanel_eventHandler(Panel* super, int ch) {
|
||||
}
|
||||
|
||||
if (reaction & HTOP_REDRAW_BAR) {
|
||||
MainPanel_updateTreeFunctions(this, this->state->settings->treeView);
|
||||
MainPanel_updateLabels(this, settings->ss->treeView, this->state->pl->incFilter);
|
||||
}
|
||||
if (reaction & HTOP_RESIZE) {
|
||||
result |= RESIZE;
|
||||
@ -182,7 +189,7 @@ static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) {
|
||||
if (hideFunctionBar && !this->inc->active)
|
||||
return;
|
||||
|
||||
IncSet_drawBar(this->inc);
|
||||
IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);
|
||||
if (this->state->pauseProcessUpdate) {
|
||||
FunctionBar_append("PAUSED", CRT_colors[PAUSED]);
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ typedef bool(*MainPanel_ForeachProcessFn)(Process*, Arg);
|
||||
|
||||
#define MainPanel_getFunctionBar(this_) (((Panel*)(this_))->defaultBar)
|
||||
|
||||
void MainPanel_updateTreeFunctions(MainPanel* this, bool mode);
|
||||
// update the Label Keys in the MainPanel bar, list: list / tree mode, filter: filter (inc) active / inactive
|
||||
void MainPanel_updateLabels(MainPanel* this, bool list, bool filter);
|
||||
|
||||
int MainPanel_selectedPid(MainPanel* this);
|
||||
|
||||
|
@ -74,6 +74,7 @@ myhtopsources = \
|
||||
ProcessLocksScreen.c \
|
||||
RichString.c \
|
||||
ScreenManager.c \
|
||||
ScreensPanel.c \
|
||||
Settings.c \
|
||||
SignalsPanel.c \
|
||||
SwapMeter.c \
|
||||
@ -135,6 +136,7 @@ myhtopheaders = \
|
||||
ProvideCurses.h \
|
||||
RichString.h \
|
||||
ScreenManager.h \
|
||||
ScreensPanel.h \
|
||||
Settings.h \
|
||||
SignalsPanel.h \
|
||||
SwapMeter.h \
|
||||
@ -153,6 +155,7 @@ linux_platform_headers = \
|
||||
generic/gettime.h \
|
||||
generic/hostname.h \
|
||||
generic/uname.h \
|
||||
linux/CGroupUtils.h \
|
||||
linux/HugePageMeter.h \
|
||||
linux/IOPriority.h \
|
||||
linux/IOPriorityPanel.h \
|
||||
@ -174,6 +177,7 @@ linux_platform_sources = \
|
||||
generic/gettime.c \
|
||||
generic/hostname.c \
|
||||
generic/uname.c \
|
||||
linux/CGroupUtils.c \
|
||||
linux/HugePageMeter.c \
|
||||
linux/IOPriorityPanel.c \
|
||||
linux/LibSensors.c \
|
||||
|
10
Meter.c
10
Meter.c
@ -155,7 +155,7 @@ ListItem* Meter_toListItem(const Meter* this, bool moving) {
|
||||
static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
|
||||
const char* caption = Meter_getCaption(this);
|
||||
attrset(CRT_colors[METER_TEXT]);
|
||||
mvaddnstr(y, x, caption, w - 1);
|
||||
mvaddnstr(y, x, caption, w);
|
||||
attrset(CRT_colors[RESET_COLOR]);
|
||||
|
||||
int captionLen = strlen(caption);
|
||||
@ -166,7 +166,7 @@ static void TextMeterMode_draw(Meter* this, int x, int y, int w) {
|
||||
|
||||
RichString_begin(out);
|
||||
Meter_displayBuffer(this, &out);
|
||||
RichString_printoffnVal(out, y, x, 0, w - 1);
|
||||
RichString_printoffnVal(out, y, x, 0, w);
|
||||
RichString_delete(&out);
|
||||
}
|
||||
|
||||
@ -176,7 +176,6 @@ static const char BarMeterMode_characters[] = "|#*@$%&.";
|
||||
|
||||
static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
|
||||
const char* caption = Meter_getCaption(this);
|
||||
w -= 2;
|
||||
attrset(CRT_colors[METER_TEXT]);
|
||||
int captionLen = 3;
|
||||
mvaddnstr(y, x, caption, captionLen);
|
||||
@ -184,10 +183,11 @@ static void BarMeterMode_draw(Meter* this, int x, int y, int w) {
|
||||
w -= captionLen;
|
||||
attrset(CRT_colors[BAR_BORDER]);
|
||||
mvaddch(y, x, '[');
|
||||
w--;
|
||||
mvaddch(y, x + MAXIMUM(w, 0), ']');
|
||||
w--;
|
||||
attrset(CRT_colors[RESET_COLOR]);
|
||||
|
||||
w--;
|
||||
x++;
|
||||
|
||||
if (w < 1)
|
||||
@ -329,7 +329,7 @@ static void GraphMeterMode_draw(Meter* this, int x, int y, int w) {
|
||||
data->values[nValues - 1] = value;
|
||||
}
|
||||
|
||||
int i = nValues - (w * 2) + 2, k = 0;
|
||||
int i = nValues - (w * 2), k = 0;
|
||||
if (i < 0) {
|
||||
k = -i / 2;
|
||||
i = 0;
|
||||
|
7
Meter.h
7
Meter.h
@ -134,6 +134,13 @@ typedef enum {
|
||||
LAST_METERMODE
|
||||
} MeterModeId;
|
||||
|
||||
typedef enum {
|
||||
RATESTATUS_DATA,
|
||||
RATESTATUS_INIT,
|
||||
RATESTATUS_NODATA,
|
||||
RATESTATUS_STALE
|
||||
} MeterRateStatus;
|
||||
|
||||
extern const MeterClass Meter_class;
|
||||
|
||||
Meter* Meter_new(const ProcessList* pl, unsigned int param, const MeterClass* type);
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "CRT.h"
|
||||
#include "Macros.h"
|
||||
#include "Meter.h"
|
||||
#include "Object.h"
|
||||
#include "Platform.h"
|
||||
#include "Process.h"
|
||||
@ -18,8 +19,7 @@ static const int NetworkIOMeter_attributes[] = {
|
||||
METER_VALUE_IOWRITE,
|
||||
};
|
||||
|
||||
static bool hasData = false;
|
||||
|
||||
static MeterRateStatus status = RATESTATUS_INIT;
|
||||
static uint32_t cached_rxb_diff;
|
||||
static uint32_t cached_rxp_diff;
|
||||
static uint32_t cached_txb_diff;
|
||||
@ -31,7 +31,7 @@ static void NetworkIOMeter_updateValues(Meter* this) {
|
||||
|
||||
uint64_t passedTimeInMs = pl->realtimeMs - cached_last_update;
|
||||
|
||||
/* update only every 500ms */
|
||||
/* update only every 500ms to have a sane span for rate calculation */
|
||||
if (passedTimeInMs > 500) {
|
||||
static uint64_t cached_rxb_total;
|
||||
static uint64_t cached_rxp_total;
|
||||
@ -39,11 +39,20 @@ static void NetworkIOMeter_updateValues(Meter* this) {
|
||||
static uint64_t cached_txp_total;
|
||||
uint64_t diff;
|
||||
|
||||
NetworkIOData data;
|
||||
if (!Platform_getNetworkIO(&data)) {
|
||||
status = RATESTATUS_NODATA;
|
||||
} else if (cached_last_update == 0) {
|
||||
status = RATESTATUS_INIT;
|
||||
} else if (passedTimeInMs > 30000) {
|
||||
status = RATESTATUS_STALE;
|
||||
} else {
|
||||
status = RATESTATUS_DATA;
|
||||
}
|
||||
|
||||
cached_last_update = pl->realtimeMs;
|
||||
|
||||
NetworkIOData data;
|
||||
hasData = Platform_getNetworkIO(&data);
|
||||
if (!hasData) {
|
||||
if (status == RATESTATUS_NODATA) {
|
||||
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data");
|
||||
return;
|
||||
}
|
||||
@ -85,6 +94,15 @@ static void NetworkIOMeter_updateValues(Meter* this) {
|
||||
cached_txp_total = data.packetsTransmitted;
|
||||
}
|
||||
|
||||
if (status == RATESTATUS_INIT) {
|
||||
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init");
|
||||
return;
|
||||
}
|
||||
if (status == RATESTATUS_STALE) {
|
||||
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale");
|
||||
return;
|
||||
}
|
||||
|
||||
this->values[0] = cached_rxb_diff;
|
||||
this->values[1] = cached_txb_diff;
|
||||
if (cached_rxb_diff + cached_txb_diff > this->total) {
|
||||
@ -98,9 +116,18 @@ static void NetworkIOMeter_updateValues(Meter* this) {
|
||||
}
|
||||
|
||||
static void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
|
||||
if (!hasData) {
|
||||
switch (status) {
|
||||
case RATESTATUS_NODATA:
|
||||
RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data");
|
||||
return;
|
||||
case RATESTATUS_INIT:
|
||||
RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing...");
|
||||
return;
|
||||
case RATESTATUS_STALE:
|
||||
RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data");
|
||||
return;
|
||||
case RATESTATUS_DATA:
|
||||
break;
|
||||
}
|
||||
|
||||
char buffer[64];
|
||||
|
@ -9,13 +9,16 @@ in the source distribution for its full text.
|
||||
|
||||
#include "OpenFilesScreen.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "Macros.h"
|
||||
#include "Panel.h"
|
||||
@ -25,7 +28,7 @@ in the source distribution for its full text.
|
||||
|
||||
|
||||
typedef struct OpenFiles_Data_ {
|
||||
char* data[7];
|
||||
char* data[8];
|
||||
} OpenFiles_Data;
|
||||
|
||||
typedef struct OpenFiles_ProcessData_ {
|
||||
@ -55,6 +58,8 @@ static size_t getIndexForType(char type) {
|
||||
return 5;
|
||||
case 't':
|
||||
return 6;
|
||||
case 'o':
|
||||
return 7;
|
||||
}
|
||||
|
||||
/* should never reach here */
|
||||
@ -74,7 +79,7 @@ OpenFilesScreen* OpenFilesScreen_new(const Process* process) {
|
||||
} else {
|
||||
this->pid = process->pid;
|
||||
}
|
||||
return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE NODE NAME");
|
||||
return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE OFFSET NODE NAME");
|
||||
}
|
||||
|
||||
void OpenFilesScreen_delete(Object* this) {
|
||||
@ -115,13 +120,14 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
|
||||
close(fdnull);
|
||||
char buffer[32] = {0};
|
||||
xSnprintf(buffer, sizeof(buffer), "%d", pid);
|
||||
execlp("lsof", "lsof", "-P", "-p", buffer, "-F", NULL);
|
||||
execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", NULL);
|
||||
exit(127);
|
||||
}
|
||||
close(fdpair[1]);
|
||||
|
||||
OpenFiles_Data* item = &(pdata->data);
|
||||
OpenFiles_FileData* fdata = NULL;
|
||||
bool lsofIncludesFileSize = false;
|
||||
|
||||
FILE* fd = fdopen(fdpair[0], "r");
|
||||
if (!fd) {
|
||||
@ -155,8 +161,17 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
|
||||
case 't': /* file's type */
|
||||
{
|
||||
size_t index = getIndexForType(cmd);
|
||||
free(item->data[index]);
|
||||
item->data[index] = xStrdup(line + 1);
|
||||
free_and_xStrdup(&item->data[index], line + 1);
|
||||
break;
|
||||
}
|
||||
case 'o': /* file's offset */
|
||||
{
|
||||
size_t index = getIndexForType(cmd);
|
||||
if (String_startsWith(line + 1, "0t")) {
|
||||
free_and_xStrdup(&item->data[index], line + 3);
|
||||
} else {
|
||||
free_and_xStrdup(&item->data[index], line + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'c': /* process command name */
|
||||
@ -166,7 +181,6 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
|
||||
case 'k': /* link count */
|
||||
case 'l': /* file's lock status */
|
||||
case 'L': /* process login name */
|
||||
case 'o': /* file's offset */
|
||||
case 'p': /* process ID */
|
||||
case 'P': /* protocol name */
|
||||
case 'R': /* parent process ID */
|
||||
@ -175,15 +189,20 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
|
||||
/* ignore */
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmd == 's')
|
||||
lsofIncludesFileSize = true;
|
||||
|
||||
free(line);
|
||||
}
|
||||
fclose(fd);
|
||||
|
||||
int wstatus;
|
||||
if (waitpid(child, &wstatus, 0) == -1) {
|
||||
pdata->error = 1;
|
||||
return pdata;
|
||||
}
|
||||
while (waitpid(child, &wstatus, 0) == -1)
|
||||
if (errno != EINTR) {
|
||||
pdata->error = 1;
|
||||
return pdata;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(wstatus)) {
|
||||
pdata->error = 1;
|
||||
@ -191,6 +210,25 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
|
||||
pdata->error = WEXITSTATUS(wstatus);
|
||||
}
|
||||
|
||||
/* We got all information we need; no post-processing needed */
|
||||
if (lsofIncludesFileSize)
|
||||
return pdata;
|
||||
|
||||
/* On linux, `lsof -o -F` omits SIZE, so add it back. */
|
||||
/* On macOS, `lsof -o -F` includes SIZE, so this block isn't needed. If no open files have a filesize, this will still run, unfortunately. */
|
||||
size_t fileSizeIndex = getIndexForType('s');
|
||||
for (fdata = pdata->files; fdata != NULL; fdata = fdata->next) {
|
||||
item = &fdata->data;
|
||||
const char* filename = getDataForType(item, 'n');
|
||||
|
||||
struct stat st;
|
||||
if (stat(filename, &st) == 0) {
|
||||
char fileSizeBuf[21]; /* 20 (long long) + 1 (NULL) */
|
||||
xSnprintf(fileSizeBuf, sizeof(fileSizeBuf), "%"PRIu64, st.st_size); /* st.st_size is long long on macOS, long on linux */
|
||||
free_and_xStrdup(&item->data[fileSizeIndex], fileSizeBuf);
|
||||
}
|
||||
}
|
||||
|
||||
return pdata;
|
||||
}
|
||||
|
||||
@ -213,14 +251,15 @@ static void OpenFilesScreen_scan(InfoScreen* this) {
|
||||
while (fdata) {
|
||||
OpenFiles_Data* data = &fdata->data;
|
||||
size_t lenN = strlen(getDataForType(data, 'n'));
|
||||
size_t sizeEntry = 5 + 7 + 4 + 10 + 10 + 10 + lenN + 7 /*spaces*/ + 1 /*null*/;
|
||||
size_t sizeEntry = 5 + 7 + 4 + 10 + 10 + 10 + 10 + lenN + 8 /*spaces*/ + 1 /*null*/;
|
||||
char entry[sizeEntry];
|
||||
xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %s",
|
||||
xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %10.10s %s",
|
||||
getDataForType(data, 'f'),
|
||||
getDataForType(data, 't'),
|
||||
getDataForType(data, 'a'),
|
||||
getDataForType(data, 'D'),
|
||||
getDataForType(data, 's'),
|
||||
getDataForType(data, 'o'),
|
||||
getDataForType(data, 'i'),
|
||||
getDataForType(data, 'n'));
|
||||
InfoScreen_addLine(this, entry);
|
||||
|
22
Panel.c
22
Panel.c
@ -49,6 +49,8 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
|
||||
this->y = y;
|
||||
this->w = w;
|
||||
this->h = h;
|
||||
this->cursorX = 0;
|
||||
this->cursorY = 0;
|
||||
this->eventHandlerState = NULL;
|
||||
this->items = Vector_new(type, owner, DEFAULT_SIZE);
|
||||
this->scrollV = 0;
|
||||
@ -57,6 +59,7 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
|
||||
this->oldSelected = 0;
|
||||
this->selectedLen = 0;
|
||||
this->needsRedraw = true;
|
||||
this->cursorOn = false;
|
||||
this->wasFocus = false;
|
||||
RichString_beginAllocated(this->header);
|
||||
this->defaultBar = fuBar;
|
||||
@ -72,6 +75,11 @@ void Panel_done(Panel* this) {
|
||||
RichString_delete(&this->header);
|
||||
}
|
||||
|
||||
void Panel_setCursorToSelection(Panel* this) {
|
||||
this->cursorY = this->y + this->selected - this->scrollV + 1;
|
||||
this->cursorX = this->x + this->selectedLen - this->scrollH;
|
||||
}
|
||||
|
||||
void Panel_setSelectionColor(Panel* this, ColorElements colorId) {
|
||||
this->selectionColorId = colorId;
|
||||
}
|
||||
@ -330,7 +338,6 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
|
||||
this->oldSelected = this->selected;
|
||||
this->wasFocus = focus;
|
||||
this->needsRedraw = false;
|
||||
move(0, 0);
|
||||
}
|
||||
|
||||
static int Panel_headerHeight(const Panel* this) {
|
||||
@ -485,3 +492,16 @@ HandlerResult Panel_selectByTyping(Panel* this, int ch) {
|
||||
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
int Panel_getCh(Panel* this) {
|
||||
if (this->cursorOn) {
|
||||
move(this->cursorY, this->cursorX);
|
||||
curs_set(1);
|
||||
} else {
|
||||
curs_set(0);
|
||||
}
|
||||
#ifdef HAVE_SET_ESCDELAY
|
||||
set_escdelay(25);
|
||||
#endif
|
||||
return getch();
|
||||
}
|
||||
|
10
Panel.h
10
Panel.h
@ -39,6 +39,10 @@ typedef enum HandlerResult_ {
|
||||
#define EVENT_IS_HEADER_CLICK(ev_) ((ev_) >= -10000 && (ev_) <= -9000)
|
||||
#define EVENT_HEADER_CLICK_GET_X(ev_) ((ev_) + 10000)
|
||||
|
||||
#define EVENT_SCREEN_TAB_CLICK(x_) (-20000 + (x_))
|
||||
#define EVENT_IS_SCREEN_TAB_CLICK(ev_) ((ev_) >= -20000 && (ev_) < -10000)
|
||||
#define EVENT_SCREEN_TAB_GET_X(ev_) ((ev_) + 20000)
|
||||
|
||||
typedef HandlerResult (*Panel_EventHandler)(Panel*, int);
|
||||
typedef void (*Panel_DrawFunctionBar)(Panel*, bool);
|
||||
typedef void (*Panel_PrintHeader)(Panel*);
|
||||
@ -61,6 +65,7 @@ typedef struct PanelClass_ {
|
||||
struct Panel_ {
|
||||
Object super;
|
||||
int x, y, w, h;
|
||||
int cursorX, cursorY;
|
||||
Vector* items;
|
||||
int selected;
|
||||
int oldSelected;
|
||||
@ -69,6 +74,7 @@ struct Panel_ {
|
||||
int scrollV;
|
||||
int scrollH;
|
||||
bool needsRedraw;
|
||||
bool cursorOn;
|
||||
bool wasFocus;
|
||||
FunctionBar* currentBar;
|
||||
FunctionBar* defaultBar;
|
||||
@ -90,6 +96,8 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
|
||||
|
||||
void Panel_done(Panel* this);
|
||||
|
||||
void Panel_setCursorToSelection(Panel* this);
|
||||
|
||||
void Panel_setSelectionColor(Panel* this, ColorElements colorId);
|
||||
|
||||
void Panel_setHeader(Panel* this, const char* header);
|
||||
@ -130,4 +138,6 @@ bool Panel_onKey(Panel* this, int key);
|
||||
|
||||
HandlerResult Panel_selectByTyping(Panel* this, int ch);
|
||||
|
||||
int Panel_getCh(Panel* this);
|
||||
|
||||
#endif
|
||||
|
210
Process.c
210
Process.c
@ -41,17 +41,33 @@ static const char* const kthreadID = "KTHREAD";
|
||||
|
||||
static uid_t Process_getuid = (uid_t)-1;
|
||||
|
||||
int Process_pidDigits = 7;
|
||||
int Process_pidDigits = PROCESS_MIN_PID_DIGITS;
|
||||
int Process_uidDigits = PROCESS_MIN_UID_DIGITS;
|
||||
|
||||
void Process_setupColumnWidths() {
|
||||
int maxPid = Platform_getMaxPid();
|
||||
if (maxPid == -1)
|
||||
return;
|
||||
|
||||
Process_pidDigits = ceil(log10(maxPid));
|
||||
if (maxPid < (int)pow(10, PROCESS_MIN_PID_DIGITS)) {
|
||||
Process_pidDigits = PROCESS_MIN_PID_DIGITS;
|
||||
return;
|
||||
}
|
||||
|
||||
Process_pidDigits = (int)log10(maxPid) + 1;
|
||||
assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS);
|
||||
}
|
||||
|
||||
void Process_setUidColumnWidth(uid_t maxUid) {
|
||||
if (maxUid < (uid_t)pow(10, PROCESS_MIN_UID_DIGITS)) {
|
||||
Process_uidDigits = PROCESS_MIN_UID_DIGITS;
|
||||
return;
|
||||
}
|
||||
|
||||
Process_uidDigits = (int)log10(maxUid) + 1;
|
||||
assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS);
|
||||
}
|
||||
|
||||
void Process_printBytes(RichString* str, unsigned long long number, bool coloring) {
|
||||
char buffer[16];
|
||||
int len;
|
||||
@ -195,7 +211,12 @@ void Process_printTime(RichString* str, unsigned long long totalHundredths, bool
|
||||
int years = days / 365;
|
||||
int daysLeft = days - 365 * years;
|
||||
|
||||
if (daysLeft >= 100) {
|
||||
if (years >= 10000000) {
|
||||
RichString_appendnAscii(str, yearColor, "eternity ", 9);
|
||||
} else if (years >= 1000) {
|
||||
len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years);
|
||||
RichString_appendnAscii(str, yearColor, buffer, len);
|
||||
} else if (daysLeft >= 100) {
|
||||
len = xSnprintf(buffer, sizeof(buffer), "%3dy", years);
|
||||
RichString_appendnAscii(str, yearColor, buffer, len);
|
||||
len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft);
|
||||
@ -381,7 +402,7 @@ static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr
|
||||
* This function makes the merged Command string. It also stores the offsets of the
|
||||
* basename, comm w.r.t the merged Command string - these offsets will be used by
|
||||
* Process_writeCommand() for coloring. The merged Command string is also
|
||||
* returned by Process_getCommandStr() for searching, sorting and filtering.
|
||||
* returned by Process_getCommand() for searching, sorting and filtering.
|
||||
*/
|
||||
void Process_makeCommandStr(Process* this) {
|
||||
ProcessMergedCommand* mc = &this->mergedCommand;
|
||||
@ -399,9 +420,9 @@ void Process_makeCommandStr(Process* this) {
|
||||
* - a user thread and showThreadNames is not set */
|
||||
if (Process_isKernelThread(this))
|
||||
return;
|
||||
if (this->state == 'Z' && !this->mergedCommand.str)
|
||||
if (this->state == ZOMBIE && !this->mergedCommand.str)
|
||||
return;
|
||||
if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames))
|
||||
if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames) && (mc->prevMergeSet == showMergedCommand))
|
||||
return;
|
||||
|
||||
/* this->mergedCommand.str needs updating only if its state or contents changed.
|
||||
@ -500,11 +521,14 @@ void Process_makeCommandStr(Process* this) {
|
||||
assert(cmdlineBasenameStart <= (int)strlen(cmdline));
|
||||
|
||||
if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
|
||||
if (showMergedCommand && (!Process_isUserlandThread(this) || showThreadNames) && !procExe && procComm && strlen(procComm)) { /* Prefix column with comm */
|
||||
if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && procComm && strlen(procComm)) { /* set column to or prefix it with comm */
|
||||
if (strncmp(cmdline + cmdlineBasenameStart, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) {
|
||||
WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
|
||||
str = stpcpy(str, procComm);
|
||||
|
||||
if(!showMergedCommand)
|
||||
return;
|
||||
|
||||
WRITE_SEPARATOR;
|
||||
}
|
||||
}
|
||||
@ -713,23 +737,44 @@ void Process_printLeftAlignedField(RichString* str, int attr, const char* conten
|
||||
RichString_appendChr(str, attr, ' ', width + 1 - columns);
|
||||
}
|
||||
|
||||
void Process_printPercentage(float val, char* buffer, int n, int* attr) {
|
||||
void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr) {
|
||||
if (val >= 0) {
|
||||
if (val < 99.9F) {
|
||||
if (val < 0.05F) {
|
||||
*attr = CRT_colors[PROCESS_SHADOW];
|
||||
}
|
||||
xSnprintf(buffer, n, "%4.1f ", val);
|
||||
} else if (val < 999) {
|
||||
*attr = CRT_colors[PROCESS_MEGABYTES];
|
||||
xSnprintf(buffer, n, "%3d. ", (int)val);
|
||||
xSnprintf(buffer, n, "%*.1f ", width, val);
|
||||
} else {
|
||||
*attr = CRT_colors[PROCESS_MEGABYTES];
|
||||
xSnprintf(buffer, n, "%4d ", (int)val);
|
||||
if (val < 100.0F)
|
||||
val = 100.0F; // Don't round down and display "val" as "99".
|
||||
xSnprintf(buffer, n, "%*.0f ", width, val);
|
||||
}
|
||||
} else {
|
||||
*attr = CRT_colors[PROCESS_SHADOW];
|
||||
xSnprintf(buffer, n, " N/A ");
|
||||
xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
|
||||
}
|
||||
}
|
||||
|
||||
static inline char processStateChar(ProcessState state) {
|
||||
switch (state) {
|
||||
case UNKNOWN: return '?';
|
||||
case RUNNABLE: return 'U';
|
||||
case RUNNING: return 'R';
|
||||
case QUEUED: return 'Q';
|
||||
case WAITING: return 'W';
|
||||
case UNINTERRUPTIBLE_WAIT: return 'D';
|
||||
case BLOCKED: return 'B';
|
||||
case PAGING: return 'P';
|
||||
case STOPPED: return 'T';
|
||||
case TRACED: return 't';
|
||||
case ZOMBIE: return 'Z';
|
||||
case DEFUNCT: return 'X';
|
||||
case IDLE: return 'I';
|
||||
case SLEEPING: return 'S';
|
||||
default:
|
||||
assert(0);
|
||||
return '!';
|
||||
}
|
||||
}
|
||||
|
||||
@ -746,7 +791,8 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
|
||||
attr = CRT_colors[PROCESS_THREAD];
|
||||
baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
|
||||
}
|
||||
if (!this->settings->treeView || this->indent == 0) {
|
||||
const ScreenSettings* ss = this->settings->ss;
|
||||
if (!ss->treeView || this->indent == 0) {
|
||||
Process_writeCommand(this, attr, baseattr, str);
|
||||
return;
|
||||
}
|
||||
@ -830,7 +876,15 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
|
||||
Process_printLeftAlignedField(str, attr, cwd, 25);
|
||||
return;
|
||||
}
|
||||
case ELAPSED: Process_printTime(str, /* convert to hundreds of a second */ this->processList->realtimeMs / 10 - 100 * this->starttime_ctime, coloring); return;
|
||||
case ELAPSED: {
|
||||
const uint64_t rt = this->processList->realtimeMs;
|
||||
const uint64_t st = this->starttime_ctime * 1000;
|
||||
const uint64_t dt =
|
||||
rt < st ? 0 :
|
||||
rt - st;
|
||||
Process_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);
|
||||
return;
|
||||
}
|
||||
case MAJFLT: Process_printCount(str, this->majflt, coloring); return;
|
||||
case MINFLT: Process_printCount(str, this->minflt, coloring); return;
|
||||
case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return;
|
||||
@ -847,13 +901,13 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
|
||||
|
||||
xSnprintf(buffer, n, "%4ld ", this->nlwp);
|
||||
break;
|
||||
case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, &attr); break;
|
||||
case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break;
|
||||
case PERCENT_NORM_CPU: {
|
||||
float cpuPercentage = this->percent_cpu / this->processList->activeCPUs;
|
||||
Process_printPercentage(cpuPercentage, buffer, n, &attr);
|
||||
Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr);
|
||||
break;
|
||||
}
|
||||
case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, &attr); break;
|
||||
case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break;
|
||||
case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break;
|
||||
case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pid); break;
|
||||
case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->ppid); break;
|
||||
@ -867,25 +921,35 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
|
||||
case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break;
|
||||
case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break;
|
||||
case STATE:
|
||||
xSnprintf(buffer, n, "%c ", this->state);
|
||||
xSnprintf(buffer, n, "%c ", processStateChar(this->state));
|
||||
switch (this->state) {
|
||||
#ifdef HTOP_NETBSD
|
||||
case 'P':
|
||||
#else
|
||||
case 'R':
|
||||
#endif
|
||||
case RUNNABLE:
|
||||
case RUNNING:
|
||||
case TRACED:
|
||||
attr = CRT_colors[PROCESS_RUN_STATE];
|
||||
break;
|
||||
case 'D':
|
||||
|
||||
case BLOCKED:
|
||||
case DEFUNCT:
|
||||
case STOPPED:
|
||||
case UNINTERRUPTIBLE_WAIT:
|
||||
case ZOMBIE:
|
||||
attr = CRT_colors[PROCESS_D_STATE];
|
||||
break;
|
||||
case 'I':
|
||||
case 'S':
|
||||
|
||||
case QUEUED:
|
||||
case WAITING:
|
||||
case IDLE:
|
||||
case SLEEPING:
|
||||
attr = CRT_colors[PROCESS_SHADOW];
|
||||
break;
|
||||
|
||||
case UNKNOWN:
|
||||
case PAGING:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ST_UID: xSnprintf(buffer, n, "%5d ", this->st_uid); break;
|
||||
case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break;
|
||||
case TIME: Process_printTime(str, this->time, coloring); return;
|
||||
case TGID:
|
||||
if (this->tgid == this->pid)
|
||||
@ -908,11 +972,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
|
||||
attr = CRT_colors[PROCESS_SHADOW];
|
||||
|
||||
if (this->user) {
|
||||
Process_printLeftAlignedField(str, attr, this->user, 9);
|
||||
Process_printLeftAlignedField(str, attr, this->user, 10);
|
||||
return;
|
||||
}
|
||||
|
||||
xSnprintf(buffer, n, "%-9d ", this->st_uid);
|
||||
xSnprintf(buffer, n, "%-10d ", this->st_uid);
|
||||
break;
|
||||
default:
|
||||
if (DynamicColumn_writeField(this, str, field))
|
||||
@ -926,7 +990,7 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
|
||||
|
||||
void Process_display(const Object* cast, RichString* out) {
|
||||
const Process* this = (const Process*) cast;
|
||||
const ProcessField* fields = this->settings->fields;
|
||||
const ProcessField* fields = this->settings->ss->fields;
|
||||
for (int i = 0; fields[i]; i++)
|
||||
As_Process(this)->writeField(this, out, fields[i]);
|
||||
|
||||
@ -962,7 +1026,7 @@ void Process_done(Process* this) {
|
||||
/* This function returns the string displayed in Command column, so that sorting
|
||||
* happens on what is displayed - whether comm, full path, basename, etc.. So
|
||||
* this follows Process_writeField(COMM) and Process_writeCommand */
|
||||
const char* Process_getCommandStr(const Process* this) {
|
||||
const char* Process_getCommand(const Process* this) {
|
||||
if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) {
|
||||
return this->cmdline;
|
||||
}
|
||||
@ -978,7 +1042,6 @@ const ProcessClass Process_class = {
|
||||
.compare = Process_compare
|
||||
},
|
||||
.writeField = Process_writeField,
|
||||
.getCommandStr = Process_getCommandStr,
|
||||
};
|
||||
|
||||
void Process_init(Process* this, const Settings* settings) {
|
||||
@ -1044,8 +1107,9 @@ int Process_compare(const void* v1, const void* v2) {
|
||||
const Process* p2 = (const Process*)v2;
|
||||
|
||||
const Settings* settings = p1->settings;
|
||||
const ScreenSettings* ss = settings->ss;
|
||||
|
||||
ProcessField key = Settings_getActiveSortKey(settings);
|
||||
ProcessField key = ScreenSettings_getActiveSortKey(ss);
|
||||
|
||||
int result = Process_compareByKey(p1, p2, key);
|
||||
|
||||
@ -1053,45 +1117,7 @@ int Process_compare(const void* v1, const void* v2) {
|
||||
if (!result)
|
||||
return SPACESHIP_NUMBER(p1->pid, p2->pid);
|
||||
|
||||
return (Settings_getActiveDirection(settings) == 1) ? result : -result;
|
||||
}
|
||||
|
||||
static uint8_t stateCompareValue(char state) {
|
||||
switch (state) {
|
||||
|
||||
case 'S':
|
||||
return 10;
|
||||
|
||||
case 'I':
|
||||
return 9;
|
||||
|
||||
case 'X':
|
||||
return 8;
|
||||
|
||||
case 'Z':
|
||||
return 7;
|
||||
|
||||
case 't':
|
||||
return 6;
|
||||
|
||||
case 'T':
|
||||
return 5;
|
||||
|
||||
case 'L':
|
||||
return 4;
|
||||
|
||||
case 'D':
|
||||
return 3;
|
||||
|
||||
case 'R':
|
||||
return 2;
|
||||
|
||||
case '?':
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;
|
||||
}
|
||||
|
||||
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {
|
||||
@ -1148,7 +1174,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
|
||||
r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
|
||||
return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
|
||||
case STATE:
|
||||
return SPACESHIP_NUMBER(stateCompareValue(p1->state), stateCompareValue(p2->state));
|
||||
return SPACESHIP_NUMBER(p1->state, p2->state);
|
||||
case ST_UID:
|
||||
return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid);
|
||||
case TIME:
|
||||
@ -1163,6 +1189,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField
|
||||
case USER:
|
||||
return SPACESHIP_NULLSTR(p1->user, p2->user);
|
||||
default:
|
||||
CRT_debug("Process_compareByKey_Base() called with key %d", key);
|
||||
assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */
|
||||
return SPACESHIP_NUMBER(p1->pid, p2->pid);
|
||||
}
|
||||
@ -1238,3 +1265,36 @@ void Process_updateExe(Process* this, const char* exe) {
|
||||
}
|
||||
this->mergedCommand.exeChanged = true;
|
||||
}
|
||||
|
||||
uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 };
|
||||
|
||||
void Process_resetFieldWidths() {
|
||||
for (size_t i = 0; i < LAST_PROCESSFIELD; i++) {
|
||||
if (!Process_fields[i].autoWidth)
|
||||
continue;
|
||||
|
||||
size_t len = strlen(Process_fields[i].title);
|
||||
assert(len <= UINT8_MAX);
|
||||
Process_fieldWidths[i] = (uint8_t)len;
|
||||
}
|
||||
}
|
||||
|
||||
void Process_updateFieldWidth(ProcessField key, size_t width) {
|
||||
if (width > UINT8_MAX)
|
||||
Process_fieldWidths[key] = UINT8_MAX;
|
||||
else if (width > Process_fieldWidths[key])
|
||||
Process_fieldWidths[key] = (uint8_t)width;
|
||||
}
|
||||
|
||||
void Process_updateCPUFieldWidths(float percentage) {
|
||||
if (percentage < 99.9) {
|
||||
Process_updateFieldWidth(PERCENT_CPU, 4);
|
||||
Process_updateFieldWidth(PERCENT_NORM_CPU, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t width = ceil(log10(percentage + .2));
|
||||
|
||||
Process_updateFieldWidth(PERCENT_CPU, width);
|
||||
Process_updateFieldWidth(PERCENT_NORM_CPU, width);
|
||||
}
|
||||
|
67
Process.h
67
Process.h
@ -60,6 +60,26 @@ typedef enum ProcessField_ {
|
||||
LAST_PROCESSFIELD
|
||||
} ProcessField;
|
||||
|
||||
/* Core process states (shared by platforms)
|
||||
* NOTE: The enum has an ordering that is important!
|
||||
* See processStateChar in process.c for ProcessSate -> letter mapping */
|
||||
typedef enum ProcessState_ {
|
||||
UNKNOWN = 1,
|
||||
RUNNABLE,
|
||||
RUNNING,
|
||||
QUEUED,
|
||||
WAITING,
|
||||
UNINTERRUPTIBLE_WAIT,
|
||||
BLOCKED,
|
||||
PAGING,
|
||||
STOPPED,
|
||||
TRACED,
|
||||
ZOMBIE,
|
||||
DEFUNCT,
|
||||
IDLE,
|
||||
SLEEPING
|
||||
} ProcessState;
|
||||
|
||||
struct Settings_;
|
||||
|
||||
/* Holds information about regions of the cmdline that should be
|
||||
@ -202,20 +222,8 @@ typedef struct Process_ {
|
||||
/* Number of major faults the process has made which have required loading a memory page from disk */
|
||||
unsigned long int majflt;
|
||||
|
||||
/*
|
||||
* Process state (platform dependent):
|
||||
* D - Waiting
|
||||
* I - Idle
|
||||
* L - Acquiring lock
|
||||
* R - Running
|
||||
* S - Sleeping
|
||||
* T - Stopped (on a signal)
|
||||
* X - Dead
|
||||
* Z - Zombie
|
||||
* t - Tracing stop
|
||||
* ? - Unknown
|
||||
*/
|
||||
char state;
|
||||
/* Process state enum field (platform dependent) */
|
||||
ProcessState state;
|
||||
|
||||
/* Whether the process was updated during the current scan */
|
||||
bool updated;
|
||||
@ -242,10 +250,10 @@ typedef struct Process_ {
|
||||
* Internal state for tree-mode.
|
||||
*/
|
||||
int indent;
|
||||
unsigned int tree_left;
|
||||
unsigned int tree_right;
|
||||
unsigned int tree_depth;
|
||||
unsigned int tree_index;
|
||||
|
||||
/* Has no known parent process */
|
||||
bool isRoot;
|
||||
|
||||
/*
|
||||
* Internal state for merged Command display
|
||||
@ -271,6 +279,9 @@ typedef struct ProcessFieldData_ {
|
||||
|
||||
/* Whether the column should be sorted in descending order by default */
|
||||
bool defaultSortDesc;
|
||||
|
||||
/* Whether the column width is dynamically adjusted (the minimum width is determined by the title length) */
|
||||
bool autoWidth;
|
||||
} ProcessFieldData;
|
||||
|
||||
// Implemented in platform-specific code:
|
||||
@ -278,24 +289,26 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field
|
||||
int Process_compare(const void* v1, const void* v2);
|
||||
void Process_delete(Object* cast);
|
||||
extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];
|
||||
extern uint8_t Process_fieldWidths[LAST_PROCESSFIELD];
|
||||
#define PROCESS_MIN_PID_DIGITS 5
|
||||
#define PROCESS_MAX_PID_DIGITS 19
|
||||
#define PROCESS_MIN_UID_DIGITS 5
|
||||
#define PROCESS_MAX_UID_DIGITS 19
|
||||
extern int Process_pidDigits;
|
||||
extern int Process_uidDigits;
|
||||
|
||||
typedef Process* (*Process_New)(const struct Settings_*);
|
||||
typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField);
|
||||
typedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField);
|
||||
typedef const char* (*Process_GetCommandStr)(const Process*);
|
||||
|
||||
typedef struct ProcessClass_ {
|
||||
const ObjectClass super;
|
||||
const Process_WriteField writeField;
|
||||
const Process_CompareByKey compareByKey;
|
||||
const Process_GetCommandStr getCommandStr;
|
||||
} ProcessClass;
|
||||
|
||||
#define As_Process(this_) ((const ProcessClass*)((this_)->super.klass))
|
||||
|
||||
#define Process_getCommand(this_) (As_Process(this_)->getCommandStr ? As_Process(this_)->getCommandStr((const Process*)(this_)) : Process_getCommandStr((const Process*)(this_)))
|
||||
#define Process_compareByKey(p1_, p2_, key_) (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_))
|
||||
|
||||
static inline pid_t Process_getParentPid(const Process* this) {
|
||||
@ -337,6 +350,9 @@ static inline bool Process_isThread(const Process* this) {
|
||||
|
||||
void Process_setupColumnWidths(void);
|
||||
|
||||
/* Sets the size of the UID column based on the passed UID */
|
||||
void Process_setUidColumnWidth(uid_t maxUid);
|
||||
|
||||
/* Takes number in bytes (base 1024). Prints 6 columns. */
|
||||
void Process_printBytes(RichString* str, unsigned long long number, bool coloring);
|
||||
|
||||
@ -356,7 +372,7 @@ void Process_fillStarttimeBuffer(Process* this);
|
||||
|
||||
void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width);
|
||||
|
||||
void Process_printPercentage(float val, char* buffer, int n, int* attr);
|
||||
void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr);
|
||||
|
||||
void Process_display(const Object* cast, RichString* out);
|
||||
|
||||
@ -382,17 +398,20 @@ int Process_pidCompare(const void* v1, const void* v2);
|
||||
|
||||
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key);
|
||||
|
||||
// Avoid direct calls, use Process_getCommand instead
|
||||
const char* Process_getCommandStr(const Process* this);
|
||||
const char* Process_getCommand(const Process* this);
|
||||
|
||||
void Process_updateComm(Process* this, const char* comm);
|
||||
void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd);
|
||||
void Process_updateExe(Process* this, const char* exe);
|
||||
|
||||
/* This function constructs the string that is displayed by
|
||||
* Process_writeCommand and also returned by Process_getCommandStr */
|
||||
* Process_writeCommand and also returned by Process_getCommand */
|
||||
void Process_makeCommandStr(Process* this);
|
||||
|
||||
void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str);
|
||||
|
||||
void Process_resetFieldWidths(void);
|
||||
void Process_updateFieldWidth(ProcessField key, size_t width);
|
||||
void Process_updateCPUFieldWidths(float percentage);
|
||||
|
||||
#endif
|
||||
|
412
ProcessList.c
412
ProcessList.c
@ -22,11 +22,10 @@ in the source distribution for its full text.
|
||||
|
||||
ProcessList* ProcessList_init(ProcessList* this, const ObjectClass* klass, UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
|
||||
this->processes = Vector_new(klass, true, DEFAULT_SIZE);
|
||||
this->processes2 = Vector_new(klass, true, DEFAULT_SIZE); // tree-view auxiliary buffer
|
||||
this->displayList = Vector_new(klass, false, DEFAULT_SIZE);
|
||||
|
||||
this->processTable = Hashtable_new(200, false);
|
||||
this->displayTreeSet = Hashtable_new(200, false);
|
||||
this->draftingTreeSet = Hashtable_new(200, false);
|
||||
this->needsSort = true;
|
||||
|
||||
this->usersTable = usersTable;
|
||||
this->pidMatchList = pidMatchList;
|
||||
@ -73,11 +72,9 @@ void ProcessList_done(ProcessList* this) {
|
||||
}
|
||||
#endif
|
||||
|
||||
Hashtable_delete(this->draftingTreeSet);
|
||||
Hashtable_delete(this->displayTreeSet);
|
||||
Hashtable_delete(this->processTable);
|
||||
|
||||
Vector_delete(this->processes2);
|
||||
Vector_delete(this->displayList);
|
||||
Vector_delete(this->processes);
|
||||
}
|
||||
|
||||
@ -105,26 +102,39 @@ static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessFiel
|
||||
if (!title)
|
||||
return "- ";
|
||||
|
||||
if (!Process_fields[field].pidColumn)
|
||||
return title;
|
||||
if (Process_fields[field].pidColumn) {
|
||||
static char titleBuffer[PROCESS_MAX_PID_DIGITS + sizeof(" ")];
|
||||
xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title);
|
||||
return titleBuffer;
|
||||
}
|
||||
|
||||
static char titleBuffer[PROCESS_MAX_PID_DIGITS + /* space */ 1 + /* null-terminator */ + 1];
|
||||
xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title);
|
||||
if (field == ST_UID) {
|
||||
static char titleBuffer[PROCESS_MAX_UID_DIGITS + sizeof(" ")];
|
||||
xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_uidDigits, title);
|
||||
return titleBuffer;
|
||||
}
|
||||
|
||||
return titleBuffer;
|
||||
if (Process_fields[field].autoWidth) {
|
||||
static char titleBuffer[UINT8_MAX + 1];
|
||||
xSnprintf(titleBuffer, sizeof(titleBuffer), "%-*.*s ", Process_fieldWidths[field], Process_fieldWidths[field], title);
|
||||
return titleBuffer;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
void ProcessList_printHeader(const ProcessList* this, RichString* header) {
|
||||
RichString_rewind(header, RichString_size(header));
|
||||
|
||||
const Settings* settings = this->settings;
|
||||
const ProcessField* fields = settings->fields;
|
||||
const ScreenSettings* ss = settings->ss;
|
||||
const ProcessField* fields = ss->fields;
|
||||
|
||||
ProcessField key = Settings_getActiveSortKey(settings);
|
||||
ProcessField key = ScreenSettings_getActiveSortKey(ss);
|
||||
|
||||
for (int i = 0; fields[i]; i++) {
|
||||
int color;
|
||||
if (settings->treeView && settings->treeViewAlwaysByPID) {
|
||||
if (ss->treeView && ss->treeViewAlwaysByPID) {
|
||||
color = CRT_colors[PANEL_HEADER_FOCUS];
|
||||
} else if (key == fields[i]) {
|
||||
color = CRT_colors[PANEL_SELECTION_FOCUS];
|
||||
@ -134,10 +144,11 @@ void ProcessList_printHeader(const ProcessList* this, RichString* header) {
|
||||
|
||||
RichString_appendWide(header, color, alignedProcessFieldTitle(this, fields[i]));
|
||||
if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') {
|
||||
bool ascending = ScreenSettings_getActiveDirection(ss) == 1;
|
||||
RichString_rewind(header, 1); // rewind to override space
|
||||
RichString_appendnWide(header,
|
||||
CRT_colors[PANEL_SELECTION_FOCUS],
|
||||
CRT_treeStr[Settings_getActiveDirection(this->settings) == 1 ? TREE_STR_ASC : TREE_STR_DESC],
|
||||
CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC],
|
||||
1);
|
||||
}
|
||||
if (COMM == fields[i] && settings->showMergedCommand) {
|
||||
@ -186,313 +197,145 @@ void ProcessList_remove(ProcessList* this, const Process* p) {
|
||||
assert(Hashtable_count(this->processTable) == Vector_count(this->processes));
|
||||
}
|
||||
|
||||
// ProcessList_updateTreeSetLayer sorts this->displayTreeSet,
|
||||
// relying only on itself.
|
||||
//
|
||||
// Algorithm
|
||||
//
|
||||
// The algorithm is based on `depth-first search`,
|
||||
// even though `breadth-first search` approach may be more efficient on first glance,
|
||||
// after comparison it may be not, as it's not safe to go deeper without first updating the tree structure.
|
||||
// If it would be safe that approach would likely bring an advantage in performance.
|
||||
//
|
||||
// Each call of the function looks for a 'layer'. A 'layer' is a list of processes with the same depth.
|
||||
// First it sorts a list. Then it runs the function recursively for each element of the sorted list.
|
||||
// After that it updates the settings of processes.
|
||||
//
|
||||
// It relies on `leftBound` and `rightBound` as an optimization to cut the list size at the time it builds a 'layer'.
|
||||
//
|
||||
// It uses a temporary Hashtable `draftingTreeSet` because it's not safe to traverse a tree
|
||||
// and at the same time make changes in it.
|
||||
//
|
||||
static void ProcessList_updateTreeSetLayer(ProcessList* this, unsigned int leftBound, unsigned int rightBound, unsigned int deep, unsigned int left, unsigned int right, unsigned int* index, unsigned int* treeIndex, int indent) {
|
||||
|
||||
// It's guaranteed that layer_size is enough space
|
||||
// but most likely it needs less. Specifically on first iteration.
|
||||
int layerSize = (right - left) / 2;
|
||||
|
||||
// check if we reach `children` of `leaves`
|
||||
if (layerSize == 0)
|
||||
return;
|
||||
|
||||
Vector* layer = Vector_new(Vector_type(this->processes), false, layerSize);
|
||||
|
||||
// Find all processes on the same layer (process with the same `deep` value
|
||||
// and included in a range from `leftBound` to `rightBound`).
|
||||
//
|
||||
// This loop also keeps track of left_bound and right_bound of these processes
|
||||
// in order not to lose this information once the list is sorted.
|
||||
//
|
||||
// The variables left_bound and right_bound are different from what the values lhs and rhs represent.
|
||||
// While left_bound and right_bound define a range of processes to look at, the values given by lhs and rhs are indices into an array
|
||||
//
|
||||
// In the below example note how filtering a range of indices i is different from filtering for processes in the bounds left_bound < x < right_bound …
|
||||
//
|
||||
// The nested tree set is sorted by left value, which is guaranteed upon entry/exit of this function.
|
||||
//
|
||||
// i | l | r
|
||||
// 1 | 1 | 9
|
||||
// 2 | 2 | 8
|
||||
// 3 | 4 | 5
|
||||
// 4 | 6 | 7
|
||||
for (unsigned int i = leftBound; i < rightBound; i++) {
|
||||
Process* proc = (Process*)Hashtable_get(this->displayTreeSet, i);
|
||||
assert(proc);
|
||||
if (proc && proc->tree_depth == deep && proc->tree_left > left && proc->tree_right < right) {
|
||||
if (Vector_size(layer) > 0) {
|
||||
Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1);
|
||||
|
||||
// Make a 'right_bound' of previous_process in a layer the current process's index.
|
||||
//
|
||||
// Use 'tree_depth' as a temporal variable.
|
||||
// It's safe to do as later 'tree_depth' will be renovated.
|
||||
previous_process->tree_depth = proc->tree_index;
|
||||
}
|
||||
|
||||
Vector_add(layer, proc);
|
||||
}
|
||||
}
|
||||
|
||||
// The loop above changes just up to process-1.
|
||||
// So the last process of the layer isn't updated by the above code.
|
||||
//
|
||||
// Thus, if present, set the `rightBound` to the last process on the layer
|
||||
if (Vector_size(layer) > 0) {
|
||||
Process* previous_process = (Process*)Vector_get(layer, Vector_size(layer) - 1);
|
||||
previous_process->tree_depth = rightBound;
|
||||
}
|
||||
|
||||
Vector_quickSort(layer);
|
||||
|
||||
int size = Vector_size(layer);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Process* proc = (Process*)Vector_get(layer, i);
|
||||
|
||||
unsigned int idx = (*index)++;
|
||||
int newLeft = (*treeIndex)++;
|
||||
|
||||
int level = deep == 0 ? 0 : (int)deep - 1;
|
||||
int currentIndent = indent == -1 ? 0 : indent | (1 << level);
|
||||
int nextIndent = indent == -1 ? 0 : ((i < size - 1) ? currentIndent : indent);
|
||||
|
||||
unsigned int newLeftBound = proc->tree_index;
|
||||
unsigned int newRightBound = proc->tree_depth;
|
||||
ProcessList_updateTreeSetLayer(this, newLeftBound, newRightBound, deep + 1, proc->tree_left, proc->tree_right, index, treeIndex, nextIndent);
|
||||
|
||||
int newRight = (*treeIndex)++;
|
||||
|
||||
proc->tree_left = newLeft;
|
||||
proc->tree_right = newRight;
|
||||
proc->tree_index = idx;
|
||||
proc->tree_depth = deep;
|
||||
|
||||
if (indent == -1) {
|
||||
proc->indent = 0;
|
||||
} else if (i == size - 1) {
|
||||
proc->indent = -currentIndent;
|
||||
} else {
|
||||
proc->indent = currentIndent;
|
||||
}
|
||||
|
||||
Hashtable_put(this->draftingTreeSet, proc->tree_index, proc);
|
||||
|
||||
// It's not strictly necessary to do this, but doing so anyways
|
||||
// allows for checking the correctness of the inner workings.
|
||||
Hashtable_remove(this->displayTreeSet, newLeftBound);
|
||||
}
|
||||
|
||||
Vector_delete(layer);
|
||||
}
|
||||
|
||||
static void ProcessList_updateTreeSet(ProcessList* this) {
|
||||
unsigned int index = 0;
|
||||
unsigned int tree_index = 1;
|
||||
|
||||
const int vsize = Vector_size(this->processes);
|
||||
|
||||
assert(Hashtable_count(this->draftingTreeSet) == 0);
|
||||
assert((int)Hashtable_count(this->displayTreeSet) == vsize);
|
||||
|
||||
ProcessList_updateTreeSetLayer(this, 0, vsize, 0, 0, vsize * 2 + 1, &index, &tree_index, -1);
|
||||
|
||||
Hashtable* tmp = this->draftingTreeSet;
|
||||
this->draftingTreeSet = this->displayTreeSet;
|
||||
this->displayTreeSet = tmp;
|
||||
|
||||
assert(Hashtable_count(this->draftingTreeSet) == 0);
|
||||
assert((int)Hashtable_count(this->displayTreeSet) == vsize);
|
||||
}
|
||||
|
||||
static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, int direction, bool show, int* node_counter, int* node_index) {
|
||||
static void ProcessList_buildTreeBranch(ProcessList* this, pid_t pid, int level, int indent, bool show) {
|
||||
// On OpenBSD the kernel thread 'swapper' has pid 0.
|
||||
// Do not treat it as root of any tree.
|
||||
if (pid == 0)
|
||||
return;
|
||||
|
||||
Vector* children = Vector_new(Class(Process), false, DEFAULT_SIZE);
|
||||
|
||||
for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
|
||||
Process* process = (Process*)Vector_get(this->processes, i);
|
||||
if (process->show && Process_isChildOf(process, pid)) {
|
||||
process = (Process*)Vector_take(this->processes, i);
|
||||
Vector_add(children, process);
|
||||
// The vector is sorted by parent PID, find the start of the range by bisection
|
||||
int vsize = Vector_size(this->processes);
|
||||
int l = 0;
|
||||
int r = vsize;
|
||||
while (l < r) {
|
||||
int c = (l + r) / 2;
|
||||
Process* process = (Process*)Vector_get(this->processes, c);
|
||||
pid_t ppid = process->isRoot ? 0 : Process_getParentPid(process);
|
||||
if (ppid < pid) {
|
||||
l = c + 1;
|
||||
} else {
|
||||
r = c;
|
||||
}
|
||||
}
|
||||
// Find the end to know the last line for indent handling purposes
|
||||
int lastShown = r;
|
||||
while (r < vsize) {
|
||||
Process* process = (Process*)Vector_get(this->processes, r);
|
||||
if (!Process_isChildOf(process, pid))
|
||||
break;
|
||||
if (process->show)
|
||||
lastShown = r;
|
||||
r++;
|
||||
}
|
||||
|
||||
int size = Vector_size(children);
|
||||
for (int i = 0; i < size; i++) {
|
||||
int index = (*node_index)++;
|
||||
Process* process = (Process*)Vector_get(children, i);
|
||||
|
||||
int lft = (*node_counter)++;
|
||||
for (int i = l; i < r; i++) {
|
||||
Process* process = (Process*)Vector_get(this->processes, i);
|
||||
|
||||
if (!show) {
|
||||
process->show = false;
|
||||
}
|
||||
|
||||
int s = Vector_size(this->processes2);
|
||||
if (direction == 1) {
|
||||
Vector_add(this->processes2, process);
|
||||
} else {
|
||||
Vector_insert(this->processes2, 0, process);
|
||||
}
|
||||
|
||||
assert(Vector_size(this->processes2) == s + 1); (void)s;
|
||||
Vector_add(this->displayList, process);
|
||||
|
||||
int nextIndent = indent | (1 << level);
|
||||
ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < size - 1) ? nextIndent : indent, direction, show ? process->showChildren : false, node_counter, node_index);
|
||||
if (i == size - 1) {
|
||||
ProcessList_buildTreeBranch(this, process->pid, level + 1, (i < lastShown) ? nextIndent : indent, process->show && process->showChildren);
|
||||
if (i == lastShown) {
|
||||
process->indent = -nextIndent;
|
||||
} else {
|
||||
process->indent = nextIndent;
|
||||
}
|
||||
|
||||
int rht = (*node_counter)++;
|
||||
|
||||
process->tree_left = lft;
|
||||
process->tree_right = rht;
|
||||
process->tree_depth = level + 1;
|
||||
process->tree_index = index;
|
||||
Hashtable_put(this->displayTreeSet, index, process);
|
||||
}
|
||||
Vector_delete(children);
|
||||
}
|
||||
|
||||
static int ProcessList_treeProcessCompare(const void* v1, const void* v2) {
|
||||
static int compareProcessByKnownParentThenNatural(const void* v1, const void* v2) {
|
||||
const Process* p1 = (const Process*)v1;
|
||||
const Process* p2 = (const Process*)v2;
|
||||
|
||||
return SPACESHIP_NUMBER(p1->tree_left, p2->tree_left);
|
||||
}
|
||||
int result = SPACESHIP_NUMBER(
|
||||
p1->isRoot ? 0 : Process_getParentPid(p1),
|
||||
p2->isRoot ? 0 : Process_getParentPid(p2)
|
||||
);
|
||||
|
||||
static int ProcessList_treeProcessCompareByPID(const void* v1, const void* v2) {
|
||||
const Process* p1 = (const Process*)v1;
|
||||
const Process* p2 = (const Process*)v2;
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return SPACESHIP_NUMBER(p1->pid, p2->pid);
|
||||
return Process_compare(v1, v2);
|
||||
}
|
||||
|
||||
// Builds a sorted tree from scratch, without relying on previously gathered information
|
||||
static void ProcessList_buildTree(ProcessList* this) {
|
||||
int node_counter = 1;
|
||||
int node_index = 0;
|
||||
int direction = Settings_getActiveDirection(this->settings);
|
||||
Vector_prune(this->displayList);
|
||||
|
||||
// Sort by PID
|
||||
Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompareByPID);
|
||||
// Mark root processes
|
||||
int vsize = Vector_size(this->processes);
|
||||
for (int i = 0; i < vsize; i++) {
|
||||
Process* process = (Process*)Vector_get(this->processes, i);
|
||||
pid_t ppid = Process_getParentPid(process);
|
||||
process->isRoot = false;
|
||||
|
||||
// Find all processes whose parent is not visible
|
||||
int size;
|
||||
while ((size = Vector_size(this->processes))) {
|
||||
int i;
|
||||
for (i = 0; i < size; i++) {
|
||||
Process* process = (Process*)Vector_get(this->processes, i);
|
||||
|
||||
// Immediately consume processes hidden from view
|
||||
if (!process->show) {
|
||||
process = (Process*)Vector_take(this->processes, i);
|
||||
process->indent = 0;
|
||||
process->tree_depth = 0;
|
||||
process->tree_left = node_counter++;
|
||||
process->tree_index = node_index++;
|
||||
Vector_add(this->processes2, process);
|
||||
ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, false, &node_counter, &node_index);
|
||||
process->tree_right = node_counter++;
|
||||
Hashtable_put(this->displayTreeSet, process->tree_index, process);
|
||||
break;
|
||||
}
|
||||
|
||||
pid_t ppid = Process_getParentPid(process);
|
||||
|
||||
// Bisect the process vector to find parent
|
||||
int l = 0;
|
||||
int r = size;
|
||||
|
||||
// If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0)
|
||||
// on Mac OS X 10.11.6) cancel bisecting and regard this process as
|
||||
// root.
|
||||
if (process->pid == ppid)
|
||||
r = 0;
|
||||
|
||||
// On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2)
|
||||
// use a ppid of 0. As that PID can't exist, we can skip searching for it.
|
||||
if (!ppid)
|
||||
r = 0;
|
||||
|
||||
while (l < r) {
|
||||
int c = (l + r) / 2;
|
||||
pid_t pid = ((Process*)Vector_get(this->processes, c))->pid;
|
||||
if (ppid == pid) {
|
||||
break;
|
||||
} else if (ppid < pid) {
|
||||
r = c;
|
||||
} else {
|
||||
l = c + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If parent not found, then construct the tree with this node as root
|
||||
if (l >= r) {
|
||||
process = (Process*)Vector_take(this->processes, i);
|
||||
process->indent = 0;
|
||||
process->tree_depth = 0;
|
||||
process->tree_left = node_counter++;
|
||||
process->tree_index = node_index++;
|
||||
Vector_add(this->processes2, process);
|
||||
Hashtable_put(this->displayTreeSet, process->tree_index, process);
|
||||
ProcessList_buildTreeBranch(this, process->pid, 0, 0, direction, process->showChildren, &node_counter, &node_index);
|
||||
process->tree_right = node_counter++;
|
||||
break;
|
||||
}
|
||||
// If PID corresponds with PPID (e.g. "kernel_task" (PID:0, PPID:0)
|
||||
// on Mac OS X 10.11.6) regard this process as root.
|
||||
if (process->pid == ppid) {
|
||||
process->isRoot = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// There should be no loop in the process tree
|
||||
assert(i < size);
|
||||
// On Linux both the init process (pid 1) and the root UMH kernel thread (pid 2)
|
||||
// use a ppid of 0. As that PID can't exist, we can skip searching for it.
|
||||
if (!ppid) {
|
||||
process->isRoot = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We don't know about its parent for whatever reason
|
||||
if (ProcessList_findProcess(this, ppid) == NULL)
|
||||
process->isRoot = true;
|
||||
}
|
||||
|
||||
// Swap listings around
|
||||
Vector* t = this->processes;
|
||||
this->processes = this->processes2;
|
||||
this->processes2 = t;
|
||||
// Sort by known parent PID (roots first), then PID
|
||||
Vector_quickSortCustomCompare(this->processes, compareProcessByKnownParentThenNatural);
|
||||
|
||||
// Find all processes whose parent is not visible
|
||||
for (int i = 0; i < vsize; i++) {
|
||||
Process* process = (Process*)Vector_get(this->processes, i);
|
||||
|
||||
// If parent not found, then construct the tree with this node as root
|
||||
if (process->isRoot) {
|
||||
process = (Process*)Vector_get(this->processes, i);
|
||||
process->indent = 0;
|
||||
process->tree_depth = 0;
|
||||
Vector_add(this->displayList, process);
|
||||
ProcessList_buildTreeBranch(this, process->pid, 0, 0, process->showChildren);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
this->needsSort = false;
|
||||
|
||||
// Check consistency of the built structures
|
||||
assert(Vector_size(this->processes) == vsize); (void)vsize;
|
||||
assert(Vector_size(this->processes2) == 0);
|
||||
assert(Vector_size(this->displayList) == vsize); (void)vsize;
|
||||
}
|
||||
|
||||
void ProcessList_sort(ProcessList* this) {
|
||||
if (this->settings->treeView) {
|
||||
ProcessList_updateTreeSet(this);
|
||||
Vector_quickSortCustomCompare(this->processes, ProcessList_treeProcessCompare);
|
||||
void ProcessList_updateDisplayList(ProcessList* this) {
|
||||
if (this->settings->ss->treeView) {
|
||||
if (this->needsSort)
|
||||
ProcessList_buildTree(this);
|
||||
} else {
|
||||
Vector_insertionSort(this->processes);
|
||||
if (this->needsSort)
|
||||
Vector_insertionSort(this->processes);
|
||||
Vector_prune(this->displayList);
|
||||
int size = Vector_size(this->processes);
|
||||
for (int i = 0; i < size; i++)
|
||||
Vector_add(this->displayList, Vector_get(this->processes, i));
|
||||
}
|
||||
this->needsSort = false;
|
||||
}
|
||||
|
||||
ProcessField ProcessList_keyAt(const ProcessList* this, int at) {
|
||||
int x = 0;
|
||||
const ProcessField* fields = this->settings->fields;
|
||||
const ProcessField* fields = this->settings->ss->fields;
|
||||
ProcessField field;
|
||||
for (int i = 0; (field = fields[i]); i++) {
|
||||
int len = strlen(alignedProcessFieldTitle(this, field));
|
||||
@ -523,6 +366,8 @@ void ProcessList_collapseAllBranches(ProcessList* this) {
|
||||
}
|
||||
|
||||
void ProcessList_rebuildPanel(ProcessList* this) {
|
||||
ProcessList_updateDisplayList(this);
|
||||
|
||||
const char* incFilter = this->incFilter;
|
||||
|
||||
const int currPos = Panel_getSelectedIndex(this->panel);
|
||||
@ -540,16 +385,16 @@ void ProcessList_rebuildPanel(ProcessList* this) {
|
||||
}
|
||||
}
|
||||
|
||||
const int processCount = Vector_size(this->processes);
|
||||
const int processCount = Vector_size(this->displayList);
|
||||
int idx = 0;
|
||||
bool foundFollowed = false;
|
||||
|
||||
for (int i = 0; i < processCount; i++) {
|
||||
Process* p = (Process*) Vector_get(this->processes, i);
|
||||
Process* p = (Process*) Vector_get(this->displayList, i);
|
||||
|
||||
if ( (!p->show)
|
||||
|| (this->userId != (uid_t) -1 && (p->st_uid != this->userId))
|
||||
|| (incFilter && !(String_contains_i(Process_getCommand(p), incFilter)))
|
||||
|| (incFilter && !(String_contains_i(Process_getCommand(p), incFilter, true)))
|
||||
|| (this->pidMatchList && !Hashtable_get(this->pidMatchList, p->tgid)) )
|
||||
continue;
|
||||
|
||||
@ -614,6 +459,7 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
|
||||
this->kernelThreads = 0;
|
||||
this->runningTasks = 0;
|
||||
|
||||
Process_resetFieldWidths();
|
||||
|
||||
// set scan timestamp
|
||||
static bool firstScanDone = false;
|
||||
@ -626,10 +472,15 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
|
||||
|
||||
ProcessList_goThroughEntries(this, false);
|
||||
|
||||
uid_t maxUid = 0;
|
||||
for (int i = Vector_size(this->processes) - 1; i >= 0; i--) {
|
||||
Process* p = (Process*) Vector_get(this->processes, i);
|
||||
Process_makeCommandStr(p);
|
||||
|
||||
// keep track of the highest UID for column scaling
|
||||
if (p->st_uid > maxUid)
|
||||
maxUid = p->st_uid;
|
||||
|
||||
if (p->tombStampMs > 0) {
|
||||
// remove tombed process
|
||||
if (this->monotonicMs >= p->tombStampMs) {
|
||||
@ -647,13 +498,6 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) {
|
||||
}
|
||||
}
|
||||
|
||||
if (this->settings->treeView) {
|
||||
// Clear out the hashtable to avoid any left-over processes from previous build
|
||||
//
|
||||
// The sorting algorithm relies on the fact that
|
||||
// len(this->displayTreeSet) == len(this->processes)
|
||||
Hashtable_clear(this->displayTreeSet);
|
||||
|
||||
ProcessList_buildTree(this);
|
||||
}
|
||||
// Set UID column width based on max UID.
|
||||
Process_setUidColumnWidth(maxUid);
|
||||
}
|
||||
|
@ -43,13 +43,13 @@ typedef unsigned long long int memory_t;
|
||||
typedef struct ProcessList_ {
|
||||
const Settings* settings;
|
||||
|
||||
Vector* processes;
|
||||
Vector* processes2;
|
||||
Hashtable* processTable;
|
||||
Vector* processes; /* all known processes; sort order can vary and differ from display order */
|
||||
Vector* displayList; /* process tree flattened in display order (borrowed);
|
||||
updated in ProcessList_updateDisplayList when rebuilding panel */
|
||||
Hashtable* processTable; /* fast known process lookup by PID */
|
||||
UsersTable* usersTable;
|
||||
|
||||
Hashtable* displayTreeSet;
|
||||
Hashtable* draftingTreeSet;
|
||||
bool needsSort;
|
||||
|
||||
Hashtable* dynamicMeters; /* runtime-discovered meters */
|
||||
Hashtable* dynamicColumns; /* runtime-discovered Columns */
|
||||
@ -108,7 +108,7 @@ void ProcessList_add(ProcessList* this, Process* p);
|
||||
|
||||
void ProcessList_remove(ProcessList* this, const Process* p);
|
||||
|
||||
void ProcessList_sort(ProcessList* this);
|
||||
void ProcessList_updateDisplayList(ProcessList* this);
|
||||
|
||||
ProcessField ProcessList_keyAt(const ProcessList* this, int at);
|
||||
|
||||
|
10
README
10
README
@ -62,6 +62,16 @@ sudo apt install libncursesw5-dev autotools-dev autoconf build-essential
|
||||
sudo dnf install ncurses-devel automake autoconf gcc
|
||||
~~~
|
||||
|
||||
**Archlinux/Manjaro**
|
||||
~~~ shell
|
||||
sudo pacman -S ncurses automake autoconf gcc
|
||||
~~~
|
||||
|
||||
**macOS**
|
||||
~~~ shell
|
||||
brew install ncurses automake autoconf gcc
|
||||
~~~
|
||||
|
||||
### Compile from source:
|
||||
To compile from source, download from the Git repository (`git clone` or downloads from [GitHub releases](https://github.com/htop-dev/htop/releases/)), then run:
|
||||
~~~ shell
|
||||
|
104
ScreenManager.c
104
ScreenManager.c
@ -16,6 +16,7 @@ in the source distribution for its full text.
|
||||
|
||||
#include "CRT.h"
|
||||
#include "FunctionBar.h"
|
||||
#include "Macros.h"
|
||||
#include "Object.h"
|
||||
#include "Platform.h"
|
||||
#include "ProcessList.h"
|
||||
@ -49,27 +50,43 @@ inline int ScreenManager_size(const ScreenManager* this) {
|
||||
}
|
||||
|
||||
void ScreenManager_add(ScreenManager* this, Panel* item, int size) {
|
||||
ScreenManager_insert(this, item, size, Vector_size(this->panels));
|
||||
}
|
||||
|
||||
void ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx) {
|
||||
int lastX = 0;
|
||||
if (this->panelCount > 0) {
|
||||
const Panel* last = (const Panel*) Vector_get(this->panels, this->panelCount - 1);
|
||||
if (idx > 0) {
|
||||
const Panel* last = (const Panel*) Vector_get(this->panels, idx - 1);
|
||||
lastX = last->x + last->w + 1;
|
||||
}
|
||||
int height = LINES - this->y1 - (this->header ? this->header->height : 0) + this->y2;
|
||||
if (size > 0) {
|
||||
Panel_resize(item, size, height);
|
||||
} else {
|
||||
Panel_resize(item, COLS - this->x1 + this->x2 - lastX, height);
|
||||
if (size <= 0) {
|
||||
size = COLS - this->x1 + this->x2 - lastX;
|
||||
}
|
||||
Panel_resize(item, size, height);
|
||||
Panel_move(item, lastX, this->y1 + (this->header ? this->header->height : 0));
|
||||
Vector_add(this->panels, item);
|
||||
if (idx < this->panelCount) {
|
||||
for (int i = idx + 1; i <= this->panelCount; i++) {
|
||||
Panel* p = (Panel*) Vector_get(this->panels, i);
|
||||
Panel_move(p, p->x + size, p->y);
|
||||
}
|
||||
}
|
||||
Vector_insert(this->panels, idx, item);
|
||||
item->needsRedraw = true;
|
||||
this->panelCount++;
|
||||
}
|
||||
|
||||
Panel* ScreenManager_remove(ScreenManager* this, int idx) {
|
||||
assert(this->panelCount > idx);
|
||||
int w = ((Panel*) Vector_get(this->panels, idx))->w;
|
||||
Panel* panel = (Panel*) Vector_remove(this->panels, idx);
|
||||
this->panelCount--;
|
||||
if (idx < this->panelCount) {
|
||||
for (int i = idx; i < this->panelCount; i++) {
|
||||
Panel* p = (Panel*) Vector_get(this->panels, i);
|
||||
Panel_move(p, p->x - w, p->y);
|
||||
}
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
@ -88,7 +105,7 @@ void ScreenManager_resize(ScreenManager* this) {
|
||||
Panel_move(panel, lastX, y1_header);
|
||||
}
|
||||
|
||||
static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut) {
|
||||
static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut, bool *force_redraw) {
|
||||
ProcessList* pl = this->header->pl;
|
||||
|
||||
Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs);
|
||||
@ -103,13 +120,18 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi
|
||||
|
||||
if (*rescan) {
|
||||
*oldTime = newTime;
|
||||
int oldUidDigits = Process_uidDigits;
|
||||
if (!this->state->pauseProcessUpdate && (*sortTimeout == 0 || this->settings->ss->treeView)) {
|
||||
pl->needsSort = true;
|
||||
*sortTimeout = 1;
|
||||
}
|
||||
// scan processes first - some header values are calculated there
|
||||
ProcessList_scan(pl, this->state->pauseProcessUpdate);
|
||||
// always update header, especially to avoid gaps in graph meters
|
||||
Header_updateData(this->header);
|
||||
if (!this->state->pauseProcessUpdate && (*sortTimeout == 0 || this->settings->treeView)) {
|
||||
ProcessList_sort(pl);
|
||||
*sortTimeout = 1;
|
||||
// force redraw if the number of UID digits was changed
|
||||
if (Process_uidDigits != oldUidDigits) {
|
||||
*force_redraw = true;
|
||||
}
|
||||
*redraw = true;
|
||||
}
|
||||
@ -120,7 +142,53 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi
|
||||
*rescan = false;
|
||||
}
|
||||
|
||||
static inline bool drawTab(int* y, int* x, int l, const char* name, bool cur) {
|
||||
attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);
|
||||
mvaddch(*y, *x, '[');
|
||||
(*x)++;
|
||||
if (*x >= l)
|
||||
return false;
|
||||
int nameLen = strlen(name);
|
||||
int n = MINIMUM(l - *x, nameLen);
|
||||
attrset(CRT_colors[cur ? SCREENS_CUR_TEXT : SCREENS_OTH_TEXT]);
|
||||
mvaddnstr(*y, *x, name, n);
|
||||
*x += n;
|
||||
if (*x >= l)
|
||||
return false;
|
||||
attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);
|
||||
mvaddch(*y, *x, ']');
|
||||
*x += 2;
|
||||
if (*x >= l)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ScreenManager_drawScreenTabs(ScreenManager* this) {
|
||||
ScreenSettings** screens = this->settings->screens;
|
||||
int cur = this->settings->ssIndex;
|
||||
int l = COLS;
|
||||
Panel* panel = (Panel*) Vector_get(this->panels, 0);
|
||||
int y = panel->y - 1;
|
||||
int x = 2;
|
||||
|
||||
if (this->name) {
|
||||
drawTab(&y, &x, l, this->name, true);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int s = 0; screens[s]; s++) {
|
||||
bool ok = drawTab(&y, &x, l, screens[s]->name, s == cur);
|
||||
if (!ok) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
attrset(CRT_colors[RESET_COLOR]);
|
||||
}
|
||||
|
||||
static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_redraw) {
|
||||
if (this->settings->screenTabs) {
|
||||
ScreenManager_drawScreenTabs(this);
|
||||
}
|
||||
const int nPanels = this->panelCount;
|
||||
for (int i = 0; i < nPanels; i++) {
|
||||
Panel* panel = (Panel*) Vector_get(this->panels, i);
|
||||
@ -133,7 +201,7 @@ static void ScreenManager_drawPanels(ScreenManager* this, int focus, bool force_
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
|
||||
void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name) {
|
||||
bool quit = false;
|
||||
int focus = 0;
|
||||
|
||||
@ -151,9 +219,11 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
|
||||
int sortTimeout = 0;
|
||||
int resetSortTimeout = 5;
|
||||
|
||||
this->name = name;
|
||||
|
||||
while (!quit) {
|
||||
if (this->header) {
|
||||
checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut);
|
||||
checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut, &force_redraw);
|
||||
}
|
||||
|
||||
if (redraw || force_redraw) {
|
||||
@ -162,10 +232,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
|
||||
}
|
||||
|
||||
int prevCh = ch;
|
||||
#ifdef HAVE_SET_ESCDELAY
|
||||
set_escdelay(25);
|
||||
#endif
|
||||
ch = getch();
|
||||
ch = Panel_getCh(panelFocus);
|
||||
|
||||
HandlerResult result = IGNORED;
|
||||
#ifdef HAVE_GETMOUSE
|
||||
@ -184,6 +251,9 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) {
|
||||
if (mevent.y == panel->y) {
|
||||
ch = EVENT_HEADER_CLICK(mevent.x - panel->x);
|
||||
break;
|
||||
} else if (this->settings->screenTabs && mevent.y == panel->y - 1) {
|
||||
ch = EVENT_SCREEN_TAB_CLICK(mevent.x);
|
||||
break;
|
||||
} else if (mevent.y > panel->y && mevent.y <= panel->y + panel->h) {
|
||||
ch = KEY_MOUSE;
|
||||
if (panel == panelFocus || this->allowFocusChange) {
|
||||
|
@ -22,6 +22,7 @@ typedef struct ScreenManager_ {
|
||||
int x2;
|
||||
int y2;
|
||||
Vector* panels;
|
||||
const char* name;
|
||||
int panelCount;
|
||||
Header* header;
|
||||
const Settings* settings;
|
||||
@ -37,10 +38,12 @@ int ScreenManager_size(const ScreenManager* this);
|
||||
|
||||
void ScreenManager_add(ScreenManager* this, Panel* item, int size);
|
||||
|
||||
void ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx);
|
||||
|
||||
Panel* ScreenManager_remove(ScreenManager* this, int idx);
|
||||
|
||||
void ScreenManager_resize(ScreenManager* this);
|
||||
|
||||
void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey);
|
||||
void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name);
|
||||
|
||||
#endif
|
||||
|
327
ScreensPanel.c
Normal file
327
ScreensPanel.c
Normal file
@ -0,0 +1,327 @@
|
||||
/*
|
||||
htop - ScreensPanel.c
|
||||
(C) 2004-2011 Hisham H. Muhammad
|
||||
(C) 2020-2022 htop dev team
|
||||
Released under the GNU GPLv2+, see the COPYING file
|
||||
in the source distribution for its full text.
|
||||
*/
|
||||
|
||||
#include "ScreensPanel.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "CRT.h"
|
||||
#include "FunctionBar.h"
|
||||
#include "Hashtable.h"
|
||||
#include "ProvideCurses.h"
|
||||
#include "Settings.h"
|
||||
#include "XUtils.h"
|
||||
|
||||
|
||||
static void ScreenListItem_delete(Object* cast) {
|
||||
ScreenListItem* this = (ScreenListItem*)cast;
|
||||
if (this->ss) {
|
||||
ScreenSettings_delete(this->ss);
|
||||
}
|
||||
ListItem_delete(cast);
|
||||
}
|
||||
|
||||
ObjectClass ScreenListItem_class = {
|
||||
.extends = Class(ListItem),
|
||||
.display = ListItem_display,
|
||||
.delete = ScreenListItem_delete,
|
||||
.compare = ListItem_compare
|
||||
};
|
||||
|
||||
ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) {
|
||||
ScreenListItem* this = AllocThis(ScreenListItem);
|
||||
ListItem_init((ListItem*)this, value, 0);
|
||||
this->ss = ss;
|
||||
return this;
|
||||
}
|
||||
|
||||
static const char* const ScreensFunctions[] = {" ", "Rename", " ", " ", "New ", " ", "MoveUp", "MoveDn", "Remove", "Done ", NULL};
|
||||
|
||||
static void ScreensPanel_delete(Object* object) {
|
||||
Panel* super = (Panel*) object;
|
||||
ScreensPanel* this = (ScreensPanel*) object;
|
||||
|
||||
/* do not delete screen settings still in use */
|
||||
int n = Panel_size(super);
|
||||
for (int i = 0; i < n; i++) {
|
||||
ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
|
||||
item->ss = NULL;
|
||||
}
|
||||
|
||||
Panel_done(super);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) {
|
||||
ScreensPanel* const this = (ScreensPanel*) super;
|
||||
|
||||
if (ch >= 32 && ch < 127 && ch != '=') {
|
||||
if (this->cursor < SCREEN_NAME_LEN - 1) {
|
||||
this->buffer[this->cursor] = (char)ch;
|
||||
this->cursor++;
|
||||
super->selectedLen = strlen(this->buffer);
|
||||
Panel_setCursorToSelection(super);
|
||||
}
|
||||
} else {
|
||||
switch(ch) {
|
||||
case 127:
|
||||
case KEY_BACKSPACE:
|
||||
{
|
||||
if (this->cursor > 0) {
|
||||
this->cursor--;
|
||||
this->buffer[this->cursor] = '\0';
|
||||
super->selectedLen = strlen(this->buffer);
|
||||
Panel_setCursorToSelection(super);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '\n':
|
||||
case '\r':
|
||||
case KEY_ENTER:
|
||||
{
|
||||
ListItem* item = (ListItem*) Panel_getSelected(super);
|
||||
if (!item)
|
||||
break;
|
||||
free(this->saved);
|
||||
item->value = xStrdup(this->buffer);
|
||||
this->renaming = false;
|
||||
super->cursorOn = false;
|
||||
Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
|
||||
ScreensPanel_update(super);
|
||||
break;
|
||||
}
|
||||
case 27: // Esc
|
||||
{
|
||||
ListItem* item = (ListItem*) Panel_getSelected(super);
|
||||
if (!item)
|
||||
break;
|
||||
item->value = this->saved;
|
||||
this->renaming = false;
|
||||
super->cursorOn = false;
|
||||
Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return HANDLED;
|
||||
}
|
||||
|
||||
static void startRenaming(Panel* super) {
|
||||
ScreensPanel* const this = (ScreensPanel*) super;
|
||||
|
||||
ListItem* item = (ListItem*) Panel_getSelected(super);
|
||||
if (item == NULL)
|
||||
return;
|
||||
this->renaming = true;
|
||||
super->cursorOn = true;
|
||||
char* name = item->value;
|
||||
this->saved = name;
|
||||
strncpy(this->buffer, name, SCREEN_NAME_LEN);
|
||||
this->buffer[SCREEN_NAME_LEN] = '\0';
|
||||
this->cursor = strlen(this->buffer);
|
||||
item->value = this->buffer;
|
||||
Panel_setSelectionColor(super, PANEL_EDIT);
|
||||
super->selectedLen = strlen(this->buffer);
|
||||
Panel_setCursorToSelection(super);
|
||||
}
|
||||
|
||||
static void rebuildSettingsArray(Panel* super) {
|
||||
ScreensPanel* const this = (ScreensPanel*) super;
|
||||
|
||||
int n = Panel_size(super);
|
||||
free(this->settings->screens);
|
||||
this->settings->screens = xMallocArray(n + 1, sizeof(ScreenSettings*));
|
||||
this->settings->screens[n] = NULL;
|
||||
for (int i = 0; i < n; i++) {
|
||||
ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
|
||||
this->settings->screens[i] = item->ss;
|
||||
}
|
||||
this->settings->nScreens = n;
|
||||
}
|
||||
|
||||
static void addNewScreen(Panel* super) {
|
||||
ScreensPanel* const this = (ScreensPanel*) super;
|
||||
|
||||
const char* name = "New";
|
||||
ScreenSettings* ss = Settings_newScreen(this->settings, &(const ScreenDefaults){ .name = name, .columns = "PID Command", .sortKey = "PID" });
|
||||
ScreenListItem* item = ScreenListItem_new(name, ss);
|
||||
int idx = Panel_getSelectedIndex(super);
|
||||
Panel_insert(super, idx + 1, (Object*) item);
|
||||
Panel_setSelected(super, idx + 1);
|
||||
}
|
||||
|
||||
static HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) {
|
||||
ScreensPanel* const this = (ScreensPanel*) super;
|
||||
|
||||
int selected = Panel_getSelectedIndex(super);
|
||||
ScreenListItem* oldFocus = (ScreenListItem*) Panel_getSelected(super);
|
||||
bool shouldRebuildArray = false;
|
||||
HandlerResult result = IGNORED;
|
||||
switch(ch) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
case KEY_ENTER:
|
||||
case KEY_MOUSE:
|
||||
case KEY_RECLICK:
|
||||
{
|
||||
this->moving = !(this->moving);
|
||||
Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS);
|
||||
ListItem* item = (ListItem*) Panel_getSelected(super);
|
||||
if (item)
|
||||
item->moving = this->moving;
|
||||
result = HANDLED;
|
||||
break;
|
||||
}
|
||||
case EVENT_SET_SELECTED:
|
||||
result = HANDLED;
|
||||
break;
|
||||
case KEY_NPAGE:
|
||||
case KEY_PPAGE:
|
||||
case KEY_HOME:
|
||||
case KEY_END: {
|
||||
Panel_onKey(super, ch);
|
||||
break;
|
||||
}
|
||||
case KEY_F(2):
|
||||
case KEY_CTRL('R'):
|
||||
{
|
||||
startRenaming(super);
|
||||
result = HANDLED;
|
||||
break;
|
||||
}
|
||||
case KEY_F(5):
|
||||
case KEY_CTRL('N'):
|
||||
{
|
||||
addNewScreen(super);
|
||||
startRenaming(super);
|
||||
shouldRebuildArray = true;
|
||||
result = HANDLED;
|
||||
break;
|
||||
}
|
||||
case KEY_UP:
|
||||
{
|
||||
if (!this->moving) {
|
||||
Panel_onKey(super, ch);
|
||||
break;
|
||||
}
|
||||
/* else fallthrough */
|
||||
} /* FALLTHRU */
|
||||
case KEY_F(7):
|
||||
case '[':
|
||||
case '-':
|
||||
{
|
||||
Panel_moveSelectedUp(super);
|
||||
shouldRebuildArray = true;
|
||||
result = HANDLED;
|
||||
break;
|
||||
}
|
||||
case KEY_DOWN:
|
||||
{
|
||||
if (!this->moving) {
|
||||
Panel_onKey(super, ch);
|
||||
break;
|
||||
}
|
||||
/* else fallthrough */
|
||||
} /* FALLTHRU */
|
||||
case KEY_F(8):
|
||||
case ']':
|
||||
case '+':
|
||||
{
|
||||
Panel_moveSelectedDown(super);
|
||||
shouldRebuildArray = true;
|
||||
result = HANDLED;
|
||||
break;
|
||||
}
|
||||
case KEY_F(9):
|
||||
//case KEY_DC:
|
||||
{
|
||||
if (Panel_size(super) > 1) {
|
||||
Panel_remove(super, selected);
|
||||
}
|
||||
shouldRebuildArray = true;
|
||||
result = HANDLED;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (ch < 255 && isalpha(ch))
|
||||
result = Panel_selectByTyping(super, ch);
|
||||
if (result == BREAK_LOOP)
|
||||
result = IGNORED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super);
|
||||
if (newFocus && oldFocus != newFocus) {
|
||||
ColumnsPanel_fill(this->columns, newFocus->ss, this->settings->dynamicColumns);
|
||||
result = HANDLED;
|
||||
}
|
||||
if (shouldRebuildArray)
|
||||
rebuildSettingsArray(super);
|
||||
if (result == HANDLED)
|
||||
ScreensPanel_update(super);
|
||||
return result;
|
||||
}
|
||||
|
||||
static HandlerResult ScreensPanel_eventHandler(Panel* super, int ch) {
|
||||
ScreensPanel* const this = (ScreensPanel*) super;
|
||||
|
||||
if (this->renaming) {
|
||||
return ScreensPanel_eventHandlerRenaming(super, ch);
|
||||
} else {
|
||||
return ScreensPanel_eventHandlerNormal(super, ch);
|
||||
}
|
||||
}
|
||||
|
||||
PanelClass ScreensPanel_class = {
|
||||
.super = {
|
||||
.extends = Class(Panel),
|
||||
.delete = ScreensPanel_delete
|
||||
},
|
||||
.eventHandler = ScreensPanel_eventHandler
|
||||
};
|
||||
|
||||
ScreensPanel* ScreensPanel_new(Settings* settings) {
|
||||
ScreensPanel* this = AllocThis(ScreensPanel);
|
||||
Panel* super = (Panel*) this;
|
||||
Hashtable* columns = settings->dynamicColumns;
|
||||
FunctionBar* fuBar = FunctionBar_new(ScreensFunctions, NULL, NULL);
|
||||
Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);
|
||||
|
||||
this->settings = settings;
|
||||
this->columns = ColumnsPanel_new(settings->screens[0], columns, &(settings->changed));
|
||||
this->moving = false;
|
||||
this->renaming = false;
|
||||
super->cursorOn = false;
|
||||
this->cursor = 0;
|
||||
Panel_setHeader(super, "Screens");
|
||||
|
||||
for (unsigned int i = 0; i < settings->nScreens; i++) {
|
||||
ScreenSettings* ss = settings->screens[i];
|
||||
char* name = ss->name;
|
||||
Panel_add(super, (Object*) ScreenListItem_new(name, ss));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void ScreensPanel_update(Panel* super) {
|
||||
ScreensPanel* this = (ScreensPanel*) super;
|
||||
int size = Panel_size(super);
|
||||
this->settings->changed = true;
|
||||
this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*));
|
||||
for (int i = 0; i < size; i++) {
|
||||
ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);
|
||||
ScreenSettings* ss = item->ss;
|
||||
free(ss->name);
|
||||
this->settings->screens[i] = ss;
|
||||
ss->name = xStrdup(((ListItem*) item)->value);
|
||||
}
|
||||
this->settings->screens[size] = NULL;
|
||||
}
|
53
ScreensPanel.h
Normal file
53
ScreensPanel.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef HEADER_ScreensPanel
|
||||
#define HEADER_ScreensPanel
|
||||
/*
|
||||
htop - ScreensPanel.h
|
||||
(C) 2004-2011 Hisham H. Muhammad
|
||||
(C) 2020-2022 htop dev team
|
||||
Released under the GNU GPLv2+, see the COPYING file
|
||||
in the source distribution for its full text.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "ColumnsPanel.h"
|
||||
#include "ListItem.h"
|
||||
#include "Object.h"
|
||||
#include "Panel.h"
|
||||
#include "ScreenManager.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#ifndef SCREEN_NAME_LEN
|
||||
#define SCREEN_NAME_LEN 20
|
||||
#endif
|
||||
|
||||
typedef struct ScreensPanel_ {
|
||||
Panel super;
|
||||
|
||||
ScreenManager* scr;
|
||||
Settings* settings;
|
||||
ColumnsPanel* columns;
|
||||
char buffer[SCREEN_NAME_LEN + 1];
|
||||
char* saved;
|
||||
int cursor;
|
||||
bool moving;
|
||||
bool renaming;
|
||||
} ScreensPanel;
|
||||
|
||||
typedef struct ScreenListItem_ {
|
||||
ListItem super;
|
||||
ScreenSettings* ss;
|
||||
} ScreenListItem;
|
||||
|
||||
|
||||
extern ObjectClass ScreenListItem_class;
|
||||
|
||||
ScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss);
|
||||
|
||||
extern PanelClass ScreensPanel_class;
|
||||
|
||||
ScreensPanel* ScreensPanel_new(Settings* settings);
|
||||
|
||||
void ScreensPanel_update(Panel* super);
|
||||
|
||||
#endif
|
347
Settings.c
347
Settings.c
@ -24,14 +24,61 @@ in the source distribution for its full text.
|
||||
#include "XUtils.h"
|
||||
|
||||
|
||||
/*
|
||||
|
||||
static char** readQuotedList(char* line) {
|
||||
int n = 0;
|
||||
char** list = xCalloc(sizeof(char*), 1);
|
||||
int start = 0;
|
||||
for (;;) {
|
||||
while (line[start] && line[start] == ' ') {
|
||||
start++;
|
||||
}
|
||||
if (line[start] != '"') {
|
||||
break;
|
||||
}
|
||||
start++;
|
||||
int close = start;
|
||||
while (line[close] && line[close] != '"') {
|
||||
close++;
|
||||
}
|
||||
int len = close - start;
|
||||
char* item = xMalloc(len + 1);
|
||||
strncpy(item, line + start, len);
|
||||
item[len] = '\0';
|
||||
list[n] = item;
|
||||
n++;
|
||||
list = xRealloc(list, sizeof(char*) * (n + 1));
|
||||
start = close + 1;
|
||||
}
|
||||
list[n] = NULL;
|
||||
return list;
|
||||
}
|
||||
|
||||
static void writeQuotedList(FILE* fd, char** list) {
|
||||
const char* sep = "";
|
||||
for (int i = 0; list[i]; i++) {
|
||||
fprintf(fd, "%s\"%s\"", sep, list[i]);
|
||||
sep = " ";
|
||||
}
|
||||
fprintf(fd, "\n");
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
void Settings_delete(Settings* this) {
|
||||
free(this->filename);
|
||||
free(this->fields);
|
||||
for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
|
||||
String_freeArray(this->hColumns[i].names);
|
||||
free(this->hColumns[i].modes);
|
||||
}
|
||||
free(this->hColumns);
|
||||
if (this->screens) {
|
||||
for (unsigned int i = 0; this->screens[i]; i++) {
|
||||
ScreenSettings_delete(this->screens[i]);
|
||||
}
|
||||
free(this->screens);
|
||||
}
|
||||
free(this);
|
||||
}
|
||||
|
||||
@ -64,14 +111,21 @@ static void Settings_readMeterModes(Settings* this, const char* line, unsigned i
|
||||
static bool Settings_validateMeters(Settings* this) {
|
||||
const size_t colCount = HeaderLayout_getColumns(this->hLayout);
|
||||
|
||||
bool anyMeter = false;
|
||||
|
||||
for (size_t column = 0; column < colCount; column++) {
|
||||
char** names = this->hColumns[column].names;
|
||||
const int* modes = this->hColumns[column].modes;
|
||||
const size_t len = this->hColumns[column].len;
|
||||
|
||||
if (!names || !modes || !len)
|
||||
if (!len)
|
||||
continue;
|
||||
|
||||
if (!names || !modes)
|
||||
return false;
|
||||
|
||||
anyMeter |= !!len;
|
||||
|
||||
// Check for each mode there is an entry with a non-NULL name
|
||||
for (size_t meterIdx = 0; meterIdx < len; meterIdx++)
|
||||
if (!names[meterIdx])
|
||||
@ -81,7 +135,7 @@ static bool Settings_validateMeters(Settings* this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return anyMeter;
|
||||
}
|
||||
|
||||
static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount) {
|
||||
@ -148,51 +202,121 @@ static void Settings_defaultMeters(Settings* this, unsigned int initialCpuCount)
|
||||
this->hColumns[1].modes[r++] = TEXT_METERMODE;
|
||||
}
|
||||
|
||||
static void Settings_readFields(Settings* settings, const char* line) {
|
||||
static const char* toFieldName(Hashtable* columns, int id) {
|
||||
if (id < 0)
|
||||
return NULL;
|
||||
if (id >= LAST_PROCESSFIELD) {
|
||||
const DynamicColumn* column = DynamicColumn_lookup(columns, id);
|
||||
return column->name;
|
||||
}
|
||||
return Process_fields[id].name;
|
||||
}
|
||||
|
||||
static int toFieldIndex(Hashtable* columns, const char* str) {
|
||||
if (isdigit(str[0])) {
|
||||
// This "+1" is for compatibility with the older enum format.
|
||||
int id = atoi(str) + 1;
|
||||
if (toFieldName(columns, id)) {
|
||||
return id;
|
||||
}
|
||||
} else {
|
||||
// Dynamically-defined columns are always stored by-name.
|
||||
char dynamic[32] = {0};
|
||||
if (sscanf(str, "Dynamic(%30s)", dynamic)) {
|
||||
char* end;
|
||||
if ((end = strrchr(dynamic, ')')) != NULL) {
|
||||
bool success;
|
||||
unsigned int key;
|
||||
*end = '\0';
|
||||
success = DynamicColumn_search(columns, dynamic, &key) != NULL;
|
||||
*end = ')';
|
||||
if (success)
|
||||
return key;
|
||||
}
|
||||
}
|
||||
// Fallback to iterative scan of table of fields by-name.
|
||||
for (int p = 1; p < LAST_PROCESSFIELD; p++) {
|
||||
const char* pName = toFieldName(columns, p);
|
||||
if (pName && strcmp(pName, str) == 0)
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, const char* line) {
|
||||
char* trim = String_trim(line);
|
||||
char** ids = String_split(trim, ' ', NULL);
|
||||
free(trim);
|
||||
|
||||
settings->flags = 0;
|
||||
/* reset default fields */
|
||||
memset(ss->fields, '\0', LAST_PROCESSFIELD * sizeof(ProcessField));
|
||||
|
||||
unsigned int i, j;
|
||||
for (j = 0, i = 0; ids[i]; i++) {
|
||||
for (size_t j = 0, i = 0; ids[i]; i++) {
|
||||
if (j >= UINT_MAX / sizeof(ProcessField))
|
||||
continue;
|
||||
if (j >= LAST_PROCESSFIELD) {
|
||||
settings->fields = xRealloc(settings->fields, j * sizeof(ProcessField));
|
||||
memset(&settings->fields[j], 0, sizeof(ProcessField));
|
||||
}
|
||||
|
||||
// Dynamically-defined columns are always stored by-name.
|
||||
char dynamic[32] = {0};
|
||||
if (sscanf(ids[i], "Dynamic(%30s)", dynamic)) {
|
||||
char* end;
|
||||
if ((end = strrchr(dynamic, ')')) == NULL)
|
||||
continue;
|
||||
*end = '\0';
|
||||
unsigned int key;
|
||||
if (!DynamicColumn_search(settings->dynamicColumns, dynamic, &key))
|
||||
continue;
|
||||
settings->fields[j++] = key;
|
||||
continue;
|
||||
}
|
||||
// This "+1" is for compatibility with the older enum format.
|
||||
int id = atoi(ids[i]) + 1;
|
||||
if (id > 0 && id < LAST_PROCESSFIELD && Process_fields[id].name) {
|
||||
settings->flags |= Process_fields[id].flags;
|
||||
settings->fields[j++] = id;
|
||||
ss->fields = xRealloc(ss->fields, (j + 1) * sizeof(ProcessField));
|
||||
memset(&ss->fields[j], 0, sizeof(ProcessField));
|
||||
}
|
||||
int id = toFieldIndex(columns, ids[i]);
|
||||
if (id >= 0)
|
||||
ss->fields[j] = id;
|
||||
if (id > 0 && id < LAST_PROCESSFIELD)
|
||||
ss->flags |= Process_fields[id].flags;
|
||||
j++;
|
||||
}
|
||||
settings->fields[j] = NULL_PROCESSFIELD;
|
||||
String_freeArray(ids);
|
||||
}
|
||||
|
||||
ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) {
|
||||
int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID;
|
||||
int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1;
|
||||
|
||||
ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));
|
||||
*ss = (ScreenSettings) {
|
||||
.name = xStrdup(defaults->name),
|
||||
.fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),
|
||||
.flags = 0,
|
||||
.direction = sortDesc ? -1 : 1,
|
||||
.treeDirection = 1,
|
||||
.sortKey = sortKey,
|
||||
.treeSortKey = PID,
|
||||
.treeView = false,
|
||||
.treeViewAlwaysByPID = false,
|
||||
.allBranchesCollapsed = false,
|
||||
};
|
||||
|
||||
ScreenSettings_readFields(ss, this->dynamicColumns, defaults->columns);
|
||||
this->screens[this->nScreens] = ss;
|
||||
this->nScreens++;
|
||||
this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1));
|
||||
this->screens[this->nScreens] = NULL;
|
||||
return ss;
|
||||
}
|
||||
|
||||
void ScreenSettings_delete(ScreenSettings* this) {
|
||||
free(this->name);
|
||||
free(this->fields);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static ScreenSettings* Settings_defaultScreens(Settings* this) {
|
||||
if (this->nScreens)
|
||||
return this->screens[0];
|
||||
for (unsigned int i = 0; i < Platform_numberOfDefaultScreens; i++) {
|
||||
const ScreenDefaults* defaults = &Platform_defaultScreens[i];
|
||||
Settings_newScreen(this, defaults);
|
||||
}
|
||||
return this->screens[0];
|
||||
}
|
||||
|
||||
static bool Settings_read(Settings* this, const char* fileName, unsigned int initialCpuCount) {
|
||||
FILE* fd = fopen(fileName, "r");
|
||||
if (!fd)
|
||||
return false;
|
||||
|
||||
ScreenSettings* screen = NULL;
|
||||
bool didReadMeters = false;
|
||||
bool didReadAny = false;
|
||||
for (;;) {
|
||||
@ -219,24 +343,40 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
|
||||
fclose(fd);
|
||||
return false;
|
||||
}
|
||||
} else if (String_eq(option[0], "fields")) {
|
||||
Settings_readFields(this, option[1]);
|
||||
} else if (String_eq(option[0], "sort_key")) {
|
||||
} else if (String_eq(option[0], "fields") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
screen = Settings_defaultScreens(this);
|
||||
ScreenSettings_readFields(screen, this->dynamicColumns, option[1]);
|
||||
} else if (String_eq(option[0], "sort_key") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
// This "+1" is for compatibility with the older enum format.
|
||||
this->sortKey = atoi(option[1]) + 1;
|
||||
} else if (String_eq(option[0], "tree_sort_key")) {
|
||||
screen = Settings_defaultScreens(this);
|
||||
screen->sortKey = atoi(option[1]) + 1;
|
||||
} else if (String_eq(option[0], "tree_sort_key") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
// This "+1" is for compatibility with the older enum format.
|
||||
this->treeSortKey = atoi(option[1]) + 1;
|
||||
} else if (String_eq(option[0], "sort_direction")) {
|
||||
this->direction = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "tree_sort_direction")) {
|
||||
this->treeDirection = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "tree_view")) {
|
||||
this->treeView = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "tree_view_always_by_pid")) {
|
||||
this->treeViewAlwaysByPID = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "all_branches_collapsed")) {
|
||||
this->allBranchesCollapsed = atoi(option[1]);
|
||||
screen = Settings_defaultScreens(this);
|
||||
screen->treeSortKey = atoi(option[1]) + 1;
|
||||
} else if (String_eq(option[0], "sort_direction") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
screen = Settings_defaultScreens(this);
|
||||
screen->direction = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "tree_sort_direction") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
screen = Settings_defaultScreens(this);
|
||||
screen->treeDirection = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "tree_view") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
screen = Settings_defaultScreens(this);
|
||||
screen->treeView = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "tree_view_always_by_pid") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
screen = Settings_defaultScreens(this);
|
||||
screen->treeViewAlwaysByPID = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "all_branches_collapsed") && this->config_version <= 2) {
|
||||
// old (no screen) naming also supported for backwards compatibility
|
||||
screen = Settings_defaultScreens(this);
|
||||
screen->allBranchesCollapsed = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "hide_kernel_threads")) {
|
||||
this->hideKernelThreads = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "hide_userland_threads")) {
|
||||
@ -267,6 +407,8 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
|
||||
this->showMergedCommand = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "header_margin")) {
|
||||
this->headerMargin = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "screen_tabs")) {
|
||||
this->screenTabs = atoi(option[1]);
|
||||
} else if (String_eq(option[0], "expand_system_time")) {
|
||||
// Compatibility option.
|
||||
this->detailedCPUTime = atoi(option[1]);
|
||||
@ -332,23 +474,49 @@ static bool Settings_read(Settings* this, const char* fileName, unsigned int ini
|
||||
} else if (String_eq(option[0], "topology_affinity")) {
|
||||
this->topologyAffinity = !!atoi(option[1]);
|
||||
#endif
|
||||
} else if (strncmp(option[0], "screen:", 7) == 0) {
|
||||
screen = Settings_newScreen(this, &(const ScreenDefaults){ .name = option[0] + 7, .columns = option[1] });
|
||||
} else if (String_eq(option[0], ".sort_key")) {
|
||||
if (screen)
|
||||
screen->sortKey = toFieldIndex(this->dynamicColumns, option[1]);
|
||||
} else if (String_eq(option[0], ".tree_sort_key")) {
|
||||
if (screen)
|
||||
screen->treeSortKey = toFieldIndex(this->dynamicColumns, option[1]);
|
||||
} else if (String_eq(option[0], ".sort_direction")) {
|
||||
if (screen)
|
||||
screen->direction = atoi(option[1]);
|
||||
} else if (String_eq(option[0], ".tree_sort_direction")) {
|
||||
if (screen)
|
||||
screen->treeDirection = atoi(option[1]);
|
||||
} else if (String_eq(option[0], ".tree_view")) {
|
||||
if (screen)
|
||||
screen->treeView = atoi(option[1]);
|
||||
} else if (String_eq(option[0], ".tree_view_always_by_pid")) {
|
||||
if (screen)
|
||||
screen->treeViewAlwaysByPID = atoi(option[1]);
|
||||
} else if (String_eq(option[0], ".all_branches_collapsed")) {
|
||||
if (screen)
|
||||
screen->allBranchesCollapsed = atoi(option[1]);
|
||||
}
|
||||
String_freeArray(option);
|
||||
}
|
||||
fclose(fd);
|
||||
if (!didReadMeters || !Settings_validateMeters(this)) {
|
||||
if (!didReadMeters || !Settings_validateMeters(this))
|
||||
Settings_defaultMeters(this, initialCpuCount);
|
||||
}
|
||||
if (!this->nScreens)
|
||||
Settings_defaultScreens(this);
|
||||
return didReadAny;
|
||||
}
|
||||
|
||||
static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, const char* name, char separator) {
|
||||
fprintf(fd, "%s=", name);
|
||||
static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns, bool byName, char separator) {
|
||||
const char* sep = "";
|
||||
for (unsigned int i = 0; fields[i]; i++) {
|
||||
if (fields[i] >= LAST_PROCESSFIELD) {
|
||||
const DynamicColumn* column = DynamicColumn_lookup(columns, fields[i]);
|
||||
fprintf(fd, "%sDynamic(%s)", sep, column->name);
|
||||
if (fields[i] < LAST_PROCESSFIELD && byName) {
|
||||
const char* pName = toFieldName(columns, fields[i]);
|
||||
fprintf(fd, "%s%s", sep, pName);
|
||||
} else if (fields[i] >= LAST_PROCESSFIELD && byName) {
|
||||
const char* pName = toFieldName(columns, fields[i]);
|
||||
fprintf(fd, " Dynamic(%s)", pName);
|
||||
} else {
|
||||
// This "-1" is for compatibility with the older enum format.
|
||||
fprintf(fd, "%s%d", sep, (int) fields[i] - 1);
|
||||
@ -358,15 +526,19 @@ static void writeFields(FILE* fd, const ProcessField* fields, Hashtable* columns
|
||||
fputc(separator, fd);
|
||||
}
|
||||
|
||||
static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) {
|
||||
static void writeList(FILE* fd, char** list, int len, char separator) {
|
||||
const char* sep = "";
|
||||
for (size_t i = 0; i < this->hColumns[column].len; i++) {
|
||||
fprintf(fd, "%s%s", sep, this->hColumns[column].names[i]);
|
||||
for (int i = 0; i < len; i++) {
|
||||
fprintf(fd, "%s%s", sep, list[i]);
|
||||
sep = " ";
|
||||
}
|
||||
fputc(separator, fd);
|
||||
}
|
||||
|
||||
static void writeMeters(const Settings* this, FILE* fd, char separator, unsigned int column) {
|
||||
writeList(fd, this->hColumns[column].names, this->hColumns[column].len, separator);
|
||||
}
|
||||
|
||||
static void writeMeterModes(const Settings* this, FILE* fd, char separator, unsigned int column) {
|
||||
const char* sep = "";
|
||||
for (size_t i = 0; i < this->hColumns[column].len; i++) {
|
||||
@ -400,12 +572,7 @@ int Settings_write(const Settings* this, bool onCrash) {
|
||||
}
|
||||
printSettingString("htop_version", VERSION);
|
||||
printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION);
|
||||
writeFields(fd, this->fields, this->dynamicColumns, "fields", separator);
|
||||
// This "-1" is for compatibility with the older enum format.
|
||||
printSettingInteger("sort_key", this->sortKey - 1);
|
||||
printSettingInteger("sort_direction", this->direction);
|
||||
printSettingInteger("tree_sort_key", this->treeSortKey - 1);
|
||||
printSettingInteger("tree_sort_direction", this->treeDirection);
|
||||
fprintf(fd, "fields="); writeFields(fd, this->screens[0]->fields, this->dynamicColumns, false, separator);
|
||||
printSettingInteger("hide_kernel_threads", this->hideKernelThreads);
|
||||
printSettingInteger("hide_userland_threads", this->hideUserlandThreads);
|
||||
printSettingInteger("shadow_other_users", this->shadowOtherUsers);
|
||||
@ -420,10 +587,8 @@ int Settings_write(const Settings* this, bool onCrash) {
|
||||
printSettingInteger("find_comm_in_cmdline", this->findCommInCmdline);
|
||||
printSettingInteger("strip_exe_from_cmdline", this->stripExeFromCmdline);
|
||||
printSettingInteger("show_merged_command", this->showMergedCommand);
|
||||
printSettingInteger("tree_view", this->treeView);
|
||||
printSettingInteger("tree_view_always_by_pid", this->treeViewAlwaysByPID);
|
||||
printSettingInteger("all_branches_collapsed", this->allBranchesCollapsed);
|
||||
printSettingInteger("header_margin", this->headerMargin);
|
||||
printSettingInteger("screen_tabs", this->screenTabs);
|
||||
printSettingInteger("detailed_cpu_time", this->detailedCPUTime);
|
||||
printSettingInteger("cpu_count_from_one", this->countCPUsFromOne);
|
||||
printSettingInteger("show_cpu_usage", this->showCPUUsage);
|
||||
@ -452,6 +617,29 @@ int Settings_write(const Settings* this, bool onCrash) {
|
||||
writeMeterModes(this, fd, separator, i);
|
||||
}
|
||||
|
||||
// Legacy compatibility with older versions of htop
|
||||
printSettingInteger("tree_view", this->screens[0]->treeView);
|
||||
// This "-1" is for compatibility with the older enum format.
|
||||
printSettingInteger("sort_key", this->screens[0]->sortKey - 1);
|
||||
printSettingInteger("tree_sort_key", this->screens[0]->treeSortKey - 1);
|
||||
printSettingInteger("sort_direction", this->screens[0]->direction);
|
||||
printSettingInteger("tree_sort_direction", this->screens[0]->treeDirection);
|
||||
printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByPID);
|
||||
printSettingInteger("all_branches_collapsed", this->screens[0]->allBranchesCollapsed);
|
||||
|
||||
for (unsigned int i = 0; i < this->nScreens; i++) {
|
||||
ScreenSettings* ss = this->screens[i];
|
||||
fprintf(fd, "screen:%s=", ss->name);
|
||||
writeFields(fd, ss->fields, this->dynamicColumns, true, separator);
|
||||
printSettingString(".sort_key", toFieldName(this->dynamicColumns, ss->sortKey));
|
||||
printSettingString(".tree_sort_key", toFieldName(this->dynamicColumns, ss->treeSortKey));
|
||||
printSettingInteger(".tree_view", ss->treeView);
|
||||
printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID);
|
||||
printSettingInteger(".sort_direction", ss->direction);
|
||||
printSettingInteger(".tree_sort_direction", ss->treeDirection);
|
||||
printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed);
|
||||
}
|
||||
|
||||
#undef printSettingString
|
||||
#undef printSettingInteger
|
||||
|
||||
@ -475,16 +663,11 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
|
||||
this->dynamicColumns = dynamicColumns;
|
||||
this->hLayout = HF_TWO_50_50;
|
||||
this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
|
||||
this->sortKey = PERCENT_CPU;
|
||||
this->treeSortKey = PID;
|
||||
this->direction = -1;
|
||||
this->treeDirection = 1;
|
||||
|
||||
this->shadowOtherUsers = false;
|
||||
this->showThreadNames = false;
|
||||
this->hideKernelThreads = true;
|
||||
this->hideUserlandThreads = false;
|
||||
this->treeView = false;
|
||||
this->allBranchesCollapsed = false;
|
||||
this->highlightBaseName = false;
|
||||
this->highlightDeletedExe = true;
|
||||
this->highlightMegabytes = true;
|
||||
@ -509,15 +692,9 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
|
||||
#ifdef HAVE_LIBHWLOC
|
||||
this->topologyAffinity = false;
|
||||
#endif
|
||||
this->fields = xCalloc(LAST_PROCESSFIELD + 1, sizeof(ProcessField));
|
||||
// TODO: turn 'fields' into a Vector,
|
||||
// (and ProcessFields into proper objects).
|
||||
this->flags = 0;
|
||||
const ProcessField* defaults = Platform_defaultFields;
|
||||
for (int i = 0; defaults[i]; i++) {
|
||||
this->fields[i] = defaults[i];
|
||||
this->flags |= Process_fields[defaults[i]].flags;
|
||||
}
|
||||
|
||||
this->screens = xCalloc(Platform_numberOfDefaultScreens * sizeof(ScreenSettings*), 1);
|
||||
this->nScreens = 0;
|
||||
|
||||
char* legacyDotfile = NULL;
|
||||
const char* rcfile = getenv("HTOPRC");
|
||||
@ -573,21 +750,27 @@ Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns)
|
||||
ok = Settings_read(this, this->filename, initialCpuCount);
|
||||
}
|
||||
if (!ok) {
|
||||
this->screenTabs = true;
|
||||
this->changed = true;
|
||||
ok = Settings_read(this, SYSCONFDIR "/htoprc", initialCpuCount);
|
||||
}
|
||||
if (!ok) {
|
||||
Settings_defaultMeters(this, initialCpuCount);
|
||||
Settings_defaultScreens(this);
|
||||
}
|
||||
|
||||
this->ssIndex = 0;
|
||||
this->ss = this->screens[this->ssIndex];
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void Settings_invertSortOrder(Settings* this) {
|
||||
void ScreenSettings_invertSortOrder(ScreenSettings* this) {
|
||||
int* attr = (this->treeView) ? &(this->treeDirection) : &(this->direction);
|
||||
*attr = (*attr == 1) ? -1 : 1;
|
||||
}
|
||||
|
||||
void Settings_setSortKey(Settings* this, ProcessField sortKey) {
|
||||
void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) {
|
||||
if (this->treeViewAlwaysByPID || !this->treeView) {
|
||||
this->sortKey = sortKey;
|
||||
this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
|
||||
|
49
Settings.h
49
Settings.h
@ -19,7 +19,13 @@ in the source distribution for its full text.
|
||||
|
||||
#define DEFAULT_DELAY 15
|
||||
|
||||
#define CONFIG_READER_MIN_VERSION 2
|
||||
#define CONFIG_READER_MIN_VERSION 3
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
const char* columns;
|
||||
const char* sortKey;
|
||||
} ScreenDefaults;
|
||||
|
||||
typedef struct {
|
||||
size_t len;
|
||||
@ -27,6 +33,19 @@ typedef struct {
|
||||
int* modes;
|
||||
} MeterColumnSetting;
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
ProcessField* fields;
|
||||
uint32_t flags;
|
||||
int direction;
|
||||
int treeDirection;
|
||||
ProcessField sortKey;
|
||||
ProcessField treeSortKey;
|
||||
bool treeView;
|
||||
bool treeViewAlwaysByPID;
|
||||
bool allBranchesCollapsed;
|
||||
} ScreenSettings;
|
||||
|
||||
typedef struct Settings_ {
|
||||
char* filename;
|
||||
int config_version;
|
||||
@ -34,16 +53,14 @@ typedef struct Settings_ {
|
||||
MeterColumnSetting* hColumns;
|
||||
Hashtable* dynamicColumns;
|
||||
|
||||
ProcessField* fields;
|
||||
uint32_t flags;
|
||||
ScreenSettings** screens;
|
||||
unsigned int nScreens;
|
||||
unsigned int ssIndex;
|
||||
ScreenSettings* ss;
|
||||
|
||||
int colorScheme;
|
||||
int delay;
|
||||
|
||||
int direction;
|
||||
int treeDirection;
|
||||
ProcessField sortKey;
|
||||
ProcessField treeSortKey;
|
||||
|
||||
bool countCPUsFromOne;
|
||||
bool detailedCPUTime;
|
||||
bool showCPUUsage;
|
||||
@ -52,9 +69,6 @@ typedef struct Settings_ {
|
||||
bool showCPUTemperature;
|
||||
bool degreeFahrenheit;
|
||||
#endif
|
||||
bool treeView;
|
||||
bool treeViewAlwaysByPID;
|
||||
bool allBranchesCollapsed;
|
||||
bool showProgramPath;
|
||||
bool shadowOtherUsers;
|
||||
bool showThreadNames;
|
||||
@ -72,6 +86,7 @@ typedef struct Settings_ {
|
||||
bool updateProcessNames;
|
||||
bool accountGuestInCPUMeter;
|
||||
bool headerMargin;
|
||||
bool screenTabs;
|
||||
#ifdef HAVE_GETMOUSE
|
||||
bool enableMouse;
|
||||
#endif
|
||||
@ -85,13 +100,13 @@ typedef struct Settings_ {
|
||||
|
||||
#define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu))
|
||||
|
||||
static inline ProcessField Settings_getActiveSortKey(const Settings* this) {
|
||||
static inline ProcessField ScreenSettings_getActiveSortKey(const ScreenSettings* this) {
|
||||
return (this->treeView)
|
||||
? (this->treeViewAlwaysByPID ? PID : this->treeSortKey)
|
||||
: this->sortKey;
|
||||
}
|
||||
|
||||
static inline int Settings_getActiveDirection(const Settings* this) {
|
||||
static inline int ScreenSettings_getActiveDirection(const ScreenSettings* this) {
|
||||
return this->treeView ? this->treeDirection : this->direction;
|
||||
}
|
||||
|
||||
@ -101,9 +116,13 @@ int Settings_write(const Settings* this, bool onCrash);
|
||||
|
||||
Settings* Settings_new(unsigned int initialCpuCount, Hashtable* dynamicColumns);
|
||||
|
||||
void Settings_invertSortOrder(Settings* this);
|
||||
ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults);
|
||||
|
||||
void Settings_setSortKey(Settings* this, ProcessField sortKey);
|
||||
void ScreenSettings_delete(ScreenSettings* this);
|
||||
|
||||
void ScreenSettings_invertSortOrder(ScreenSettings* this);
|
||||
|
||||
void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey);
|
||||
|
||||
void Settings_enableReadonly(void);
|
||||
|
||||
|
@ -18,15 +18,14 @@ in the source distribution for its full text.
|
||||
#include "XUtils.h"
|
||||
|
||||
|
||||
Panel* SignalsPanel_new() {
|
||||
Panel* SignalsPanel_new(int preSelectedSignal) {
|
||||
Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc("Send ", "Cancel "));
|
||||
const int defaultSignal = SIGTERM;
|
||||
int defaultPosition = 15;
|
||||
unsigned int i;
|
||||
for (i = 0; i < Platform_numberOfSignals; i++) {
|
||||
Panel_set(this, i, (Object*) ListItem_new(Platform_signals[i].name, Platform_signals[i].number));
|
||||
// signal 15 is not always the 15th signal in the table
|
||||
if (Platform_signals[i].number == defaultSignal) {
|
||||
if (Platform_signals[i].number == preSelectedSignal) {
|
||||
defaultPosition = i;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ Released under the GNU GPLv2+, see the COPYING file
|
||||
in the source distribution for its full text.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
|
||||
@ -15,6 +17,8 @@ typedef struct SignalItem_ {
|
||||
int number;
|
||||
} SignalItem;
|
||||
|
||||
Panel* SignalsPanel_new(void);
|
||||
#define SIGNALSPANEL_INITSELECTEDSIGNAL SIGTERM
|
||||
|
||||
Panel* SignalsPanel_new(int preSelectedSignal);
|
||||
|
||||
#endif
|
||||
|
@ -10,6 +10,7 @@ in the source distribution for its full text.
|
||||
#include "TraceScreen.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
@ -47,7 +48,9 @@ void TraceScreen_delete(Object* cast) {
|
||||
TraceScreen* this = (TraceScreen*) cast;
|
||||
if (this->child > 0) {
|
||||
kill(this->child, SIGTERM);
|
||||
waitpid(this->child, NULL, 0);
|
||||
while (waitpid(this->child, NULL, 0) == -1)
|
||||
if (errno != EINTR)
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->strace) {
|
||||
|
54
XUtils.c
54
XUtils.c
@ -94,13 +94,28 @@ void* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size)
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool String_contains_i(const char* s1, const char* s2) {
|
||||
return strcasestr(s1, s2) != NULL;
|
||||
inline bool String_contains_i(const char* s1, const char* s2, bool multi) {
|
||||
// we have a multi-string search term, handle as special case for performance reasons
|
||||
if (multi && strstr(s2, "|")) {
|
||||
size_t nNeedles;
|
||||
char** needles = String_split(s2, '|', &nNeedles);
|
||||
for (size_t i = 0; i < nNeedles; i++) {
|
||||
if (strcasestr(s1, needles[i]) != NULL) {
|
||||
String_freeArray(needles);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
String_freeArray(needles);
|
||||
return false;
|
||||
} else {
|
||||
return strcasestr(s1, s2) != NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char* String_cat(const char* s1, const char* s2) {
|
||||
const size_t l1 = strlen(s1);
|
||||
const size_t l2 = strlen(s2);
|
||||
assert(SIZE_MAX - l1 > l2);
|
||||
char* out = xMalloc(l1 + l2 + 1);
|
||||
memcpy(out, s1, l1);
|
||||
memcpy(out + l1, s2, l2);
|
||||
@ -122,10 +137,10 @@ char* String_trim(const char* in) {
|
||||
}
|
||||
|
||||
char** String_split(const char* s, char sep, size_t* n) {
|
||||
const unsigned int rate = 10;
|
||||
const size_t rate = 10;
|
||||
char** out = xCalloc(rate, sizeof(char*));
|
||||
size_t ctr = 0;
|
||||
unsigned int blocks = rate;
|
||||
size_t blocks = rate;
|
||||
const char* where;
|
||||
while ((where = strchr(s, sep)) != NULL) {
|
||||
size_t size = (size_t)(where - s);
|
||||
@ -160,36 +175,9 @@ void String_freeArray(char** s) {
|
||||
free(s);
|
||||
}
|
||||
|
||||
char* String_getToken(const char* line, const unsigned short int numMatch) {
|
||||
const size_t len = strlen(line);
|
||||
char inWord = 0;
|
||||
unsigned short int count = 0;
|
||||
char match[50];
|
||||
|
||||
size_t foundCount = 0;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char lastState = inWord;
|
||||
inWord = line[i] == ' ' ? 0 : 1;
|
||||
|
||||
if (lastState == 0 && inWord == 1)
|
||||
count++;
|
||||
|
||||
if (inWord == 1) {
|
||||
if (count == numMatch && line[i] != ' ' && line[i] != '\0' && line[i] != '\n' && line[i] != (char)EOF) {
|
||||
match[foundCount] = line[i];
|
||||
foundCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match[foundCount] = '\0';
|
||||
return xStrdup(match);
|
||||
}
|
||||
|
||||
char* String_readLine(FILE* fd) {
|
||||
const unsigned int step = 1024;
|
||||
unsigned int bufSize = step;
|
||||
const size_t step = 1024;
|
||||
size_t bufSize = step;
|
||||
char* buffer = xMalloc(step + 1);
|
||||
char* at = buffer;
|
||||
for (;;) {
|
||||
|
4
XUtils.h
4
XUtils.h
@ -40,7 +40,7 @@ static inline bool String_startsWith(const char* s, const char* match) {
|
||||
return strncmp(s, match, strlen(match)) == 0;
|
||||
}
|
||||
|
||||
bool String_contains_i(const char* s1, const char* s2);
|
||||
bool String_contains_i(const char* s1, const char* s2, bool multi);
|
||||
|
||||
static inline bool String_eq(const char* s1, const char* s2) {
|
||||
return strcmp(s1, s2) == 0;
|
||||
@ -54,8 +54,6 @@ char** String_split(const char* s, char sep, size_t* n);
|
||||
|
||||
void String_freeArray(char** s);
|
||||
|
||||
char* String_getToken(const char* line, unsigned short int numMatch) ATTR_MALLOC;
|
||||
|
||||
char* String_readLine(FILE* fd) ATTR_MALLOC;
|
||||
|
||||
/* Always null-terminates dest. Caller must pass a strictly positive size. */
|
||||
|
36
configure.ac
36
configure.ac
@ -6,13 +6,13 @@
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([htop], [3.1.1], [htop@groups.io], [], [https://htop.dev/])
|
||||
AC_INIT([htop], [3.2.0], [htop@groups.io], [], [https://htop.dev/])
|
||||
|
||||
AC_CONFIG_SRCDIR([htop.c])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
|
||||
AC_CANONICAL_TARGET
|
||||
AC_CANONICAL_HOST
|
||||
AM_INIT_AUTOMAKE([-Wall std-options subdir-objects])
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
@ -22,7 +22,7 @@ AM_INIT_AUTOMAKE([-Wall std-options subdir-objects])
|
||||
# Checks for platform.
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
case "$target_os" in
|
||||
case "$host_os" in
|
||||
linux*|gnu*)
|
||||
my_htop_platform=linux
|
||||
AC_DEFINE([HTOP_LINUX], [], [Building for Linux.])
|
||||
@ -419,13 +419,23 @@ case "$enable_unwind" in
|
||||
AC_CHECK_LIB([lzma], [lzma_index_buffer_decode])
|
||||
fi
|
||||
AC_CHECK_LIB([unwind], [backtrace], [], [enable_unwind=no])
|
||||
AC_CHECK_HEADERS([libunwind.h], [], [enable_unwind=no])
|
||||
AC_CHECK_HEADERS([libunwind.h], [], [
|
||||
old_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS -I/usr/include/libunwind"
|
||||
AC_CHECK_HEADERS([libunwind/libunwind.h], [], [
|
||||
enable_unwind=no
|
||||
CFLAGS="$old_CFLAGS"
|
||||
])
|
||||
])
|
||||
;;
|
||||
no)
|
||||
;;
|
||||
yes)
|
||||
AC_CHECK_LIB([unwind], [backtrace], [], [AC_MSG_ERROR([can not find required library libunwind])])
|
||||
AC_CHECK_HEADERS([libunwind.h], [], [AC_MSG_ERROR([can not find require header file libunwind.h])])
|
||||
AC_CHECK_HEADERS([libunwind.h], [], [
|
||||
CFLAGS="$CFLAGS -I/usr/include/libunwind"
|
||||
AC_CHECK_HEADERS([libunwind/libunwind.h], [], [AC_MSG_ERROR([can not find required header file libunwind.h])])
|
||||
])
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR([bad value '$enable_unwind' for --enable-unwind])
|
||||
@ -446,8 +456,18 @@ case "$enable_hwloc" in
|
||||
no)
|
||||
;;
|
||||
yes)
|
||||
AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
|
||||
AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
|
||||
m4_ifdef([PKG_PROG_PKG_CONFIG], [
|
||||
PKG_PROG_PKG_CONFIG()
|
||||
PKG_CHECK_MODULES(HWLOC, hwloc, [
|
||||
CFLAGS="$CFLAGS $HWLOC_CFLAGS" LIBS="$LIBS $HWLOC_LIBS"
|
||||
], [
|
||||
AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
|
||||
AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
|
||||
])
|
||||
], [
|
||||
AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([can not find required library libhwloc])])
|
||||
AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([can not find require header file hwloc.h])])
|
||||
])
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR([bad value '$enable_hwloc' for --enable-hwloc])
|
||||
@ -703,7 +723,7 @@ AC_SUBST([AM_CPPFLAGS])
|
||||
# We're done, let's go!
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2021 htop dev team."], [Copyright message.])
|
||||
AC_DEFINE_UNQUOTED([COPYRIGHT], ["(C) 2004-2019 Hisham Muhammad. (C) 2020-2022 htop dev team."], [Copyright message.])
|
||||
|
||||
AM_CONDITIONAL([HTOP_LINUX], [test "$my_htop_platform" = linux])
|
||||
AM_CONDITIONAL([HTOP_FREEBSD], [test "$my_htop_platform" = freebsd])
|
||||
|
@ -12,6 +12,7 @@ in the source distribution for its full text.
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/dirent.h>
|
||||
|
||||
#include "CRT.h"
|
||||
#include "Process.h"
|
||||
@ -26,7 +27,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, .pidColumn = true, },
|
||||
[PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, .pidColumn = true, },
|
||||
[SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, .pidColumn = true, },
|
||||
[TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = 0, },
|
||||
[TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = PROCESS_FLAG_TTY, },
|
||||
[TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, .pidColumn = true, },
|
||||
[MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, },
|
||||
[MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, },
|
||||
@ -37,11 +38,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
|
||||
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
|
||||
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
|
||||
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
|
||||
@ -276,6 +277,18 @@ static long long int nanosecondsToCentiseconds(uint64_t nanoseconds) {
|
||||
return nanoseconds / nanoseconds_per_second * centiseconds_per_second;
|
||||
}
|
||||
|
||||
static char* DarwinProcess_getDevname(dev_t dev) {
|
||||
if (dev == NODEV) {
|
||||
return NULL;
|
||||
}
|
||||
char buf[sizeof("/dev/") + MAXNAMLEN];
|
||||
char *name = devname_r(dev, S_IFCHR, buf, MAXNAMLEN);
|
||||
if (name) {
|
||||
return xStrdup(name);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) {
|
||||
DarwinProcess* dp = (DarwinProcess*)proc;
|
||||
|
||||
@ -306,15 +319,8 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
|
||||
proc->isKernelThread = false;
|
||||
proc->isUserlandThread = false;
|
||||
dp->translated = ps->kp_proc.p_flag & P_TRANSLATED;
|
||||
|
||||
proc->tty_nr = ps->kp_eproc.e_tdev;
|
||||
const char* name = (ps->kp_eproc.e_tdev != NODEV) ? devname(ps->kp_eproc.e_tdev, S_IFCHR) : NULL;
|
||||
if (!name) {
|
||||
free(proc->tty_name);
|
||||
proc->tty_name = NULL;
|
||||
} else {
|
||||
free_and_xStrdup(&proc->tty_name, name);
|
||||
}
|
||||
proc->tty_name = NULL;
|
||||
|
||||
proc->starttime_ctime = ep->p_starttime.tv_sec;
|
||||
Process_fillStarttimeBuffer(proc);
|
||||
@ -322,16 +328,33 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps,
|
||||
DarwinProcess_updateExe(ep->p_pid, proc);
|
||||
DarwinProcess_updateCmdLine(ps, proc);
|
||||
|
||||
if (proc->settings->flags & PROCESS_FLAG_CWD) {
|
||||
if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
|
||||
DarwinProcess_updateCwd(ep->p_pid, proc);
|
||||
}
|
||||
}
|
||||
|
||||
if (proc->tty_name == NULL && (dev_t)proc->tty_nr != NODEV) {
|
||||
/* The call to devname() is extremely expensive (due to lstat)
|
||||
* and represents ~95% of htop's CPU usage when there is high
|
||||
* process turnover.
|
||||
*
|
||||
* To mitigate this we only fetch TTY information if the TTY
|
||||
* field is enabled in the settings.
|
||||
*/
|
||||
if (proc->settings->ss->flags & PROCESS_FLAG_TTY) {
|
||||
proc->tty_name = DarwinProcess_getDevname(proc->tty_nr);
|
||||
if (!proc->tty_name) {
|
||||
/* devname failed: prevent us from calling it again */
|
||||
proc->tty_nr = NODEV;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Mutable information */
|
||||
proc->nice = ep->p_nice;
|
||||
proc->priority = ep->p_priority;
|
||||
|
||||
proc->state = (ep->p_stat == SZOMB) ? 'Z' : '?';
|
||||
proc->state = (ep->p_stat == SZOMB) ? ZOMBIE : UNKNOWN;
|
||||
|
||||
/* Make sure the updated flag is set */
|
||||
proc->updated = true;
|
||||
@ -354,6 +377,7 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList*
|
||||
} else {
|
||||
proc->super.percent_cpu = 0.0;
|
||||
}
|
||||
Process_updateCPUFieldWidths(proc->super.percent_cpu);
|
||||
|
||||
proc->super.time = nanosecondsToCentiseconds(total_current_time_ns);
|
||||
proc->super.nlwp = pti.pti_threadnum;
|
||||
@ -386,7 +410,7 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (proc->state == 'Z') {
|
||||
if (proc->state == ZOMBIE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -430,15 +454,15 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) {
|
||||
vm_deallocate(mach_task_self(), (vm_address_t) thread_list, sizeof(thread_port_array_t) * thread_count);
|
||||
mach_port_deallocate(mach_task_self(), port);
|
||||
|
||||
char state = '?';
|
||||
/* Taken from: https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/thread_info.h#L129 */
|
||||
switch (run_state) {
|
||||
case TH_STATE_RUNNING: state = 'R'; break;
|
||||
case TH_STATE_STOPPED: state = 'S'; break;
|
||||
case TH_STATE_WAITING: state = 'W'; break;
|
||||
case TH_STATE_UNINTERRUPTIBLE: state = 'U'; break;
|
||||
case TH_STATE_HALTED: state = 'H'; break;
|
||||
case TH_STATE_RUNNING: proc->state = RUNNING; break;
|
||||
case TH_STATE_STOPPED: proc->state = STOPPED; break;
|
||||
case TH_STATE_WAITING: proc->state = WAITING; break;
|
||||
case TH_STATE_UNINTERRUPTIBLE: proc->state = UNINTERRUPTIBLE_WAIT; break;
|
||||
case TH_STATE_HALTED: proc->state = BLOCKED; break;
|
||||
default: proc->state = UNKNOWN;
|
||||
}
|
||||
proc->state = state;
|
||||
}
|
||||
|
||||
|
||||
|
@ -13,6 +13,8 @@ in the source distribution for its full text.
|
||||
#include "darwin/DarwinProcessList.h"
|
||||
|
||||
|
||||
#define PROCESS_FLAG_TTY 0x00000100
|
||||
|
||||
typedef struct DarwinProcess_ {
|
||||
Process super;
|
||||
|
||||
|
@ -49,7 +49,15 @@ in the source distribution for its full text.
|
||||
#endif
|
||||
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
};
|
||||
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
const SignalItem Platform_signals[] = {
|
||||
{ .name = " 0 Cancel", .number = 0 },
|
||||
@ -126,7 +134,7 @@ static double Platform_nanosecondsPerMachTick = 1.0;
|
||||
|
||||
static double Platform_nanosecondsPerSchedulerTick = -1;
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
Platform_nanosecondsPerMachTick = Platform_calculateNanosecondsPerMachTick();
|
||||
|
||||
// Determine the number of scheduler clock ticks per second
|
||||
@ -139,6 +147,8 @@ void Platform_init(void) {
|
||||
|
||||
const double nanos_per_sec = 1e9;
|
||||
Platform_nanosecondsPerSchedulerTick = nanos_per_sec / scheduler_ticks_per_sec;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Converts ticks in the Mach "timebase" to nanoseconds.
|
||||
|
@ -19,13 +19,16 @@ in the source distribution for its full text.
|
||||
#include "NetworkIOMeter.h"
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "darwin/DarwinProcess.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "generic/hostname.h"
|
||||
#include "generic/uname.h"
|
||||
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
extern const SignalItem Platform_signals[];
|
||||
|
||||
@ -33,7 +36,7 @@ extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
// Converts ticks in the Mach "timebase" to nanoseconds.
|
||||
// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant.
|
||||
@ -87,8 +90,8 @@ static inline void Platform_getRelease(char** string) {
|
||||
|
||||
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
|
||||
|
||||
static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return false;
|
||||
static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
@ -2,8 +2,8 @@
|
||||
#define HEADER_PlatformHelpers
|
||||
/*
|
||||
htop - darwin/PlatformHelpers.h
|
||||
(C) 2018 Pierre Malhaire, 2020-2021 htop dev team, 2021 Alexander Momchilov
|
||||
Released under the GNU GPLv2, see the COPYING file
|
||||
(C) 2018 Pierre Malhaire, 2020-2022 htop dev team, 2021 Alexander Momchilov
|
||||
Released under the GNU GPLv2+, see the COPYING file
|
||||
in the source distribution for its full text.
|
||||
*/
|
||||
|
||||
|
@ -37,11 +37,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
|
||||
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
|
||||
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
|
||||
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
|
||||
|
@ -79,8 +79,8 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, H
|
||||
|
||||
size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
|
||||
len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len);
|
||||
dfpl->cp_time_o = xCalloc(cpus, sizeof_cp_time_array);
|
||||
dfpl->cp_time_n = xCalloc(cpus, sizeof_cp_time_array);
|
||||
dfpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
|
||||
dfpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
|
||||
len = sizeof_cp_time_array;
|
||||
|
||||
// fetch initial single (or average) CPU clicks from kernel
|
||||
@ -481,7 +481,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
DragonFlyBSDProcessList_updateExe(kproc, proc);
|
||||
DragonFlyBSDProcessList_updateProcessName(dfpl->kd, kproc, proc);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_CWD) {
|
||||
if (settings->ss->flags & PROCESS_FLAG_CWD) {
|
||||
DragonFlyBSDProcessList_updateCwd(kproc, proc);
|
||||
}
|
||||
|
||||
@ -513,6 +513,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
|
||||
proc->percent_cpu = 100.0 * ((double)kproc->kp_lwp.kl_pctcpu / (double)kernelFScale);
|
||||
proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem);
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
if (proc->percent_cpu > 0.1) {
|
||||
// system idle process should own all CPU time left regardless of CPU count
|
||||
@ -542,60 +543,61 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
}
|
||||
|
||||
// would be nice if we could store multiple states in proc->state (as enum) and have writeField render them
|
||||
/* Taken from: https://github.com/DragonFlyBSD/DragonFlyBSD/blob/c163a4d7ee9c6857ee4e04a3a2cbb50c3de29da1/sys/sys/proc_common.h */
|
||||
switch (kproc->kp_stat) {
|
||||
case SIDL: proc->state = 'I'; isIdleProcess = true; break;
|
||||
case SIDL: proc->state = IDLE; isIdleProcess = true; break;
|
||||
case SACTIVE:
|
||||
switch (kproc->kp_lwp.kl_stat) {
|
||||
case LSSLEEP:
|
||||
if (kproc->kp_lwp.kl_flags & LWP_SINTR) // interruptible wait short/long
|
||||
if (kproc->kp_lwp.kl_slptime >= MAXSLP) {
|
||||
proc->state = 'I';
|
||||
proc->state = IDLE;
|
||||
isIdleProcess = true;
|
||||
} else {
|
||||
proc->state = 'S';
|
||||
proc->state = SLEEPING;
|
||||
}
|
||||
else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) // interruptible lwkt wait
|
||||
proc->state = 'S';
|
||||
proc->state = SLEEPING;
|
||||
else if (kproc->kp_paddr) // uninterruptible wait
|
||||
proc->state = 'D';
|
||||
proc->state = UNINTERRUPTIBLE_WAIT;
|
||||
else // uninterruptible lwkt wait
|
||||
proc->state = 'B';
|
||||
proc->state = UNINTERRUPTIBLE_WAIT;
|
||||
break;
|
||||
case LSRUN:
|
||||
if (kproc->kp_lwp.kl_stat == LSRUN) {
|
||||
if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ)))
|
||||
proc->state = 'Q';
|
||||
proc->state = QUEUED;
|
||||
else
|
||||
proc->state = 'R';
|
||||
proc->state = RUNNING;
|
||||
}
|
||||
break;
|
||||
case LSSTOP:
|
||||
proc->state = 'T';
|
||||
proc->state = STOPPED;
|
||||
break;
|
||||
default:
|
||||
proc->state = 'A';
|
||||
proc->state = PAGING;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SSTOP: proc->state = 'T'; break;
|
||||
case SZOMB: proc->state = 'Z'; break;
|
||||
case SCORE: proc->state = 'C'; break;
|
||||
default: proc->state = '?';
|
||||
case SSTOP: proc->state = STOPPED; break;
|
||||
case SZOMB: proc->state = ZOMBIE; break;
|
||||
case SCORE: proc->state = BLOCKED; break;
|
||||
default: proc->state = UNKNOWN;
|
||||
}
|
||||
|
||||
if (kproc->kp_flags & P_SWAPPEDOUT)
|
||||
proc->state = 'W';
|
||||
proc->state = SLEEPING;
|
||||
if (kproc->kp_flags & P_TRACED)
|
||||
proc->state = 'T';
|
||||
proc->state = TRACED;
|
||||
if (kproc->kp_flags & P_JAILED)
|
||||
proc->state = 'J';
|
||||
proc->state = TRACED;
|
||||
|
||||
if (Process_isKernelThread(proc))
|
||||
super->kernelThreads++;
|
||||
|
||||
super->totalTasks++;
|
||||
|
||||
if (proc->state == 'R')
|
||||
if (proc->state == RUNNING)
|
||||
super->runningTasks++;
|
||||
|
||||
proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
|
||||
|
@ -32,8 +32,15 @@ in the source distribution for its full text.
|
||||
#include "dragonflybsd/DragonFlyBSDProcess.h"
|
||||
#include "dragonflybsd/DragonFlyBSDProcessList.h"
|
||||
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
};
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
const SignalItem Platform_signals[] = {
|
||||
{ .name = " 0 Cancel", .number = 0 },
|
||||
@ -105,8 +112,9 @@ const MeterClass* const Platform_meterTypes[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
/* no platform-specific setup needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_done(void) {
|
||||
|
@ -23,12 +23,15 @@ in the source distribution for its full text.
|
||||
#include "Process.h"
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "generic/hostname.h"
|
||||
#include "generic/uname.h"
|
||||
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
extern const SignalItem Platform_signals[];
|
||||
|
||||
@ -36,7 +39,7 @@ extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
void Platform_done(void);
|
||||
|
||||
@ -78,8 +81,8 @@ static inline void Platform_getRelease(char** string) {
|
||||
|
||||
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
|
||||
|
||||
static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return false;
|
||||
static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
@ -9,8 +9,8 @@ in the source distribution for its full text.
|
||||
|
||||
|
||||
#define PLATFORM_PROCESS_FIELDS \
|
||||
JID = 100, \
|
||||
JAIL = 101, \
|
||||
JID = 100, \
|
||||
JAIL = 101, \
|
||||
\
|
||||
DUMMY_BUMP_FIELD = CWD, \
|
||||
// End of list
|
||||
|
@ -36,11 +36,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
|
||||
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
|
||||
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
|
||||
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
|
||||
@ -49,6 +49,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
|
||||
[JID] = { .name = "JID", .title = "JID", .description = "Jail prison ID", .flags = 0, .pidColumn = true, },
|
||||
[JAIL] = { .name = "JAIL", .title = "JAIL ", .description = "Jail prison name", .flags = 0, },
|
||||
[EMULATION] = { .name = "EMULATION", .title = "EMULATION ", .description = "System call emulation environment (ABI)", .flags = 0, },
|
||||
};
|
||||
|
||||
Process* FreeBSDProcess_new(const Settings* settings) {
|
||||
@ -61,6 +62,7 @@ Process* FreeBSDProcess_new(const Settings* settings) {
|
||||
void Process_delete(Object* cast) {
|
||||
FreeBSDProcess* this = (FreeBSDProcess*) cast;
|
||||
Process_done((Process*)cast);
|
||||
free(this->emul);
|
||||
free(this->jname);
|
||||
free(this);
|
||||
}
|
||||
@ -77,6 +79,9 @@ static void FreeBSDProcess_writeField(const Process* this, RichString* str, Proc
|
||||
case JAIL:
|
||||
Process_printLeftAlignedField(str, attr, fp->jname ? fp->jname : "N/A", 11);
|
||||
return;
|
||||
case EMULATION:
|
||||
Process_printLeftAlignedField(str, attr, fp->emul ? fp->emul : "N/A", 16);
|
||||
return;
|
||||
default:
|
||||
Process_writeField(this, str, field);
|
||||
return;
|
||||
@ -94,6 +99,8 @@ static int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, Pro
|
||||
return SPACESHIP_NUMBER(p1->jid, p2->jid);
|
||||
case JAIL:
|
||||
return SPACESHIP_NULLSTR(p1->jname, p2->jname);
|
||||
case EMULATION:
|
||||
return SPACESHIP_NULLSTR(p1->emul, p2->emul);
|
||||
default:
|
||||
return Process_compareByKey_Base(v1, v2, key);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ typedef struct FreeBSDProcess_ {
|
||||
Process super;
|
||||
int jid;
|
||||
char* jname;
|
||||
char* emul;
|
||||
} FreeBSDProcess;
|
||||
|
||||
extern const ProcessClass FreeBSDProcess_class;
|
||||
|
@ -109,8 +109,8 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, H
|
||||
|
||||
size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
|
||||
len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len);
|
||||
fpl->cp_time_o = xCalloc(cpus, sizeof_cp_time_array);
|
||||
fpl->cp_time_n = xCalloc(cpus, sizeof_cp_time_array);
|
||||
fpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
|
||||
fpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
|
||||
len = sizeof_cp_time_array;
|
||||
|
||||
// fetch initial single (or average) CPU clicks from kernel
|
||||
@ -509,6 +509,9 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
proc->pgrp = kproc->ki_pgid;
|
||||
proc->st_uid = kproc->ki_uid;
|
||||
proc->starttime_ctime = kproc->ki_start.tv_sec;
|
||||
if (proc->starttime_ctime < 0) {
|
||||
proc->starttime_ctime = super->realtimeMs / 1000;
|
||||
}
|
||||
Process_fillStarttimeBuffer(proc);
|
||||
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
|
||||
ProcessList_add(super, proc);
|
||||
@ -516,7 +519,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
FreeBSDProcessList_updateExe(kproc, proc);
|
||||
FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_CWD) {
|
||||
if (settings->ss->flags & PROCESS_FLAG_CWD) {
|
||||
FreeBSDProcessList_updateCwd(kproc, proc);
|
||||
}
|
||||
|
||||
@ -549,6 +552,8 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
}
|
||||
}
|
||||
|
||||
free_and_xStrdup(&fp->emul, kproc->ki_emul);
|
||||
|
||||
// from FreeBSD source /src/usr.bin/top/machine.c
|
||||
proc->m_virt = kproc->ki_size / ONE_K;
|
||||
proc->m_resident = kproc->ki_rssize * pageSizeKb;
|
||||
@ -557,6 +562,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
|
||||
proc->percent_cpu = 100.0 * ((double)kproc->ki_pctcpu / (double)kernelFScale);
|
||||
proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem);
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
if (kproc->ki_stat == SRUN && kproc->ki_oncpu != NOCPU) {
|
||||
proc->processor = kproc->ki_oncpu;
|
||||
@ -578,15 +584,16 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
proc->nice = PRIO_MAX + 1 + kproc->ki_pri.pri_level - PRI_MIN_IDLE;
|
||||
}
|
||||
|
||||
/* Taken from: https://github.com/freebsd/freebsd-src/blob/1ad2d87778970582854082bcedd2df0394fd4933/sys/sys/proc.h#L851 */
|
||||
switch (kproc->ki_stat) {
|
||||
case SIDL: proc->state = 'I'; break;
|
||||
case SRUN: proc->state = 'R'; break;
|
||||
case SSLEEP: proc->state = 'S'; break;
|
||||
case SSTOP: proc->state = 'T'; break;
|
||||
case SZOMB: proc->state = 'Z'; break;
|
||||
case SWAIT: proc->state = 'D'; break;
|
||||
case SLOCK: proc->state = 'L'; break;
|
||||
default: proc->state = '?';
|
||||
case SIDL: proc->state = IDLE; break;
|
||||
case SRUN: proc->state = RUNNING; break;
|
||||
case SSLEEP: proc->state = SLEEPING; break;
|
||||
case SSTOP: proc->state = STOPPED; break;
|
||||
case SZOMB: proc->state = ZOMBIE; break;
|
||||
case SWAIT: proc->state = WAITING; break;
|
||||
case SLOCK: proc->state = BLOCKED; break;
|
||||
default: proc->state = UNKNOWN;
|
||||
}
|
||||
|
||||
if (Process_isKernelThread(proc))
|
||||
@ -595,7 +602,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
|
||||
|
||||
super->totalTasks++;
|
||||
if (proc->state == 'R')
|
||||
if (proc->state == RUNNING)
|
||||
super->runningTasks++;
|
||||
proc->updated = true;
|
||||
}
|
||||
|
@ -50,8 +50,15 @@ in the source distribution for its full text.
|
||||
#include "zfs/ZfsArcMeter.h"
|
||||
#include "zfs/ZfsCompressedArcMeter.h"
|
||||
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
};
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
const SignalItem Platform_signals[] = {
|
||||
{ .name = " 0 Cancel", .number = 0 },
|
||||
@ -127,8 +134,9 @@ const MeterClass* const Platform_meterTypes[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
/* no platform-specific setup needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_done(void) {
|
||||
@ -284,6 +292,7 @@ bool Platform_getDiskIO(DiskIOData* data) {
|
||||
if (devstat_checkversion(NULL) < 0)
|
||||
return false;
|
||||
|
||||
// use static to plug memory leak; see #841
|
||||
static struct devinfo info = { 0 };
|
||||
struct statinfo current = { .dinfo = &info };
|
||||
|
||||
|
@ -19,12 +19,15 @@ in the source distribution for its full text.
|
||||
#include "Process.h"
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "generic/hostname.h"
|
||||
#include "generic/uname.h"
|
||||
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
extern const SignalItem Platform_signals[];
|
||||
|
||||
@ -32,7 +35,7 @@ extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
void Platform_done(void);
|
||||
|
||||
@ -78,8 +81,8 @@ static inline void Platform_getRelease(char** string) {
|
||||
|
||||
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
|
||||
|
||||
static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return false;
|
||||
static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
@ -9,8 +9,9 @@ in the source distribution for its full text.
|
||||
|
||||
|
||||
#define PLATFORM_PROCESS_FIELDS \
|
||||
JID = 100, \
|
||||
JAIL = 101, \
|
||||
JID = 100, \
|
||||
JAIL = 101, \
|
||||
EMULATION = 102, \
|
||||
\
|
||||
DUMMY_BUMP_FIELD = CWD, \
|
||||
// End of list
|
||||
|
@ -49,9 +49,13 @@ void Generic_gettime_monotonic(uint64_t* msec) {
|
||||
else
|
||||
*msec = 0;
|
||||
|
||||
#else
|
||||
#else /* lower resolution gettimeofday() should be always available */
|
||||
|
||||
# error "No monotonic clock available"
|
||||
struct timeval tv;
|
||||
if (gettimeofday(&tv, NULL) == 0)
|
||||
*msec = ((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000);
|
||||
else
|
||||
*msec = 0;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ char* Generic_uname(void) {
|
||||
|
||||
if (uname_result == 0) {
|
||||
size_t written = xSnprintf(savedString, sizeof(savedString), "%s %s [%s]", uname_info.sysname, uname_info.release, uname_info.machine);
|
||||
if (!String_contains_i(savedString, distro) && sizeof(savedString) > written)
|
||||
if (!String_contains_i(savedString, distro, false) && sizeof(savedString) > written)
|
||||
snprintf(savedString + written, sizeof(savedString) - written, " @ %s", distro);
|
||||
} else {
|
||||
snprintf(savedString, sizeof(savedString), "%s", distro);
|
||||
|
65
htop.1.in
65
htop.1.in
@ -1,4 +1,4 @@
|
||||
.TH "HTOP" "1" "2021" "@PACKAGE_STRING@" "User Commands"
|
||||
.TH "HTOP" "1" "2022" "@PACKAGE_STRING@" "User Commands"
|
||||
.SH "NAME"
|
||||
htop, pcp-htop \- interactive process viewer
|
||||
.SH "SYNOPSIS"
|
||||
@ -50,7 +50,8 @@ Start
|
||||
in monochrome mode
|
||||
.TP
|
||||
\fB\-F \-\-filter=FILTER
|
||||
Filter processes by command
|
||||
Filter processes by terms matching the commands. The terms are matched
|
||||
case-insensitive and as fixed strings (not regexs). You can separate multiple terms with "|".
|
||||
.TP
|
||||
\fB\-h \-\-help
|
||||
Display a help message and exit
|
||||
@ -62,7 +63,7 @@ Show only the given PIDs
|
||||
Sort by this column (use \-\-sort\-key help for a column list).
|
||||
This will force a list view unless you specify -t at the same time.
|
||||
.TP
|
||||
\fB\-u \-\-user=USERNAME\fR
|
||||
\fB\-u \-\-user=USERNAME|UID\fR
|
||||
Show only the processes of a given user
|
||||
.TP
|
||||
\fB\-U \-\-no-unicode\fR
|
||||
@ -95,6 +96,10 @@ held.
|
||||
The following commands are supported while in
|
||||
.BR htop :
|
||||
.TP 5
|
||||
.B Tab, Shift-Tab
|
||||
Select the next / the previous screen tab to display.
|
||||
You can enable showing the screen tab names in the Setup screen (F2).
|
||||
.TP
|
||||
.B Up, Alt-k
|
||||
Select (highlight) the previous process in the process list. Scroll the list
|
||||
if necessary.
|
||||
@ -175,6 +180,8 @@ bindings take precedence.
|
||||
Incremental process filtering: type in part of a process command line and
|
||||
only processes whose names match will be shown. To cancel filtering,
|
||||
enter the Filter option again and press Esc.
|
||||
The matching is done case-insensitive. Terms are fixed strings (no regex).
|
||||
You can separate multiple terms with "|".
|
||||
.TP
|
||||
.B F5, t
|
||||
Tree view: organize processes by parenthood, and layout the relations
|
||||
@ -285,19 +292,35 @@ is active, the executable path (/proc/[pid]/exe) and the command name
|
||||
(/proc/[pid]/comm) are also shown merged with the command line, if available.
|
||||
|
||||
The program basename is highlighted if set in the configuration. Additional
|
||||
highlighting can be configured for stale executables (cf. Exe column below).
|
||||
highlighting can be configured for stale executables (cf. EXE column below).
|
||||
.TP
|
||||
.B Comm
|
||||
.B COMM
|
||||
The command name of the process obtained from /proc/[pid]/comm, if readable.
|
||||
|
||||
Requires Linux kernel 2.6.33 or newer.
|
||||
.TP
|
||||
.B Exe
|
||||
.B EXE
|
||||
The abbreviated basename of the executable of the process, obtained from
|
||||
/proc/[pid]/exe, if readable. htop is able to read this file on linux for ALL
|
||||
the processes only if it has the capability CAP_SYS_PTRACE or root privileges.
|
||||
|
||||
The basename is marked in red if the executable used to run the process has
|
||||
been replaced or deleted on disk since the process started. This additional
|
||||
markup can be configured.
|
||||
been replaced or deleted on disk since the process started. The information is
|
||||
obtained by processing the contents of /proc/[pid]/exe.
|
||||
|
||||
Furthermore the basename is marked in yellow if any library is reported as having
|
||||
been replaced or deleted on disk since it was last loaded. The information is
|
||||
obtained by processing the contents of /proc/[pid]/maps.
|
||||
|
||||
When deciding the color the replacement of the main executable always takes
|
||||
precedence over replacement of any other library. If only the memory map indicates
|
||||
a replacement of the main executable, this will show as if any other library had
|
||||
been replaced or deleted.
|
||||
|
||||
This additional color markup can be configured in the "Display Options" section of
|
||||
the setup screen.
|
||||
|
||||
Displaying EXE requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED.
|
||||
.TP
|
||||
.B PID
|
||||
The process ID.
|
||||
@ -476,7 +499,23 @@ The I/O rate of write(2) in bytes per second, for the process.
|
||||
The I/O rate, IO_READ_RATE + IO_WRITE_RATE (see above).
|
||||
.TP
|
||||
.B CGROUP
|
||||
Which cgroup the process is in.
|
||||
Which cgroup the process is in. For a shortened view see the CCGROUP column below.
|
||||
.TP
|
||||
.B CCGROUP
|
||||
Shortened view of the cgroup name that the process is in.
|
||||
This performs some pattern-based replacements to shorten the displayed string and thus condense the information.
|
||||
\fB/*.slice\fR is shortened to \fB/[*]\fR (exceptions below)
|
||||
\fB/system.slice\fR is shortened to \fB/[S]\fR
|
||||
\fB/user.slice\fR is shortened to \fB/[U]\fR
|
||||
\fB/user-*.slice\fR is shortened to \fB/[U:*]\fR (directly preceding \fB/[U]\fR before dropped)
|
||||
\fB/machine.slice\fR is shortened to \fB/[M]\fR
|
||||
\fB/machine-*.scope\fR is shortened to \fB/[SNC:*]\fR (SNC: systemd nspawn container), uppercase for the monitor
|
||||
\fB/lxc.monitor.*\fR is shortened to \fB/[LXC:*]\fR
|
||||
\fB/lxc.payload.*\fR is shortened to \fB/[lxc:*]\fR
|
||||
\fB/*.scope\fR is shortened to \fB/!*\fR
|
||||
\fB/*.service\fR is shortened to \fB/*\fR (suffix removed)
|
||||
|
||||
Encountered escape sequences (e.g. from systemd) inside the cgroup name are not decoded.
|
||||
.TP
|
||||
.B OOM
|
||||
OOM killer score.
|
||||
@ -499,12 +538,6 @@ The percentage of time spent waiting for the completion of synchronous block I/O
|
||||
.B PERCENT_SWAP_DELAY (SWAPD%)
|
||||
The percentage of time spent swapping in pages. Requires CAP_NET_ADMIN.
|
||||
.TP
|
||||
.B COMM
|
||||
The command name for the process. Requires Linux kernel 2.6.33 or newer.
|
||||
.TP
|
||||
.B EXE
|
||||
The executable file of the process as reported by the kernel. Requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED.
|
||||
.TP
|
||||
.B AGRP
|
||||
The autogroup identifier for the process. Requires Linux CFS to be enabled.
|
||||
.TP
|
||||
@ -635,7 +668,7 @@ communities, and forms part of the Performance Co-Pilot suite of tools.
|
||||
.SH "COPYRIGHT"
|
||||
Copyright \(co 2004-2019 Hisham Muhammad.
|
||||
.br
|
||||
Copyright \(co 2020-2021 htop dev team.
|
||||
Copyright \(co 2020-2022 htop dev team.
|
||||
.LP
|
||||
License GPLv2+: GNU General Public License version 2 or, at your option, any later version.
|
||||
.LP
|
||||
|
341
linux/CGroupUtils.c
Normal file
341
linux/CGroupUtils.c
Normal file
@ -0,0 +1,341 @@
|
||||
/*
|
||||
htop - CGroupUtils.h
|
||||
(C) 2021 htop dev team
|
||||
Released under the GNU GPLv2+, see the COPYING file
|
||||
in the source distribution for its full text.
|
||||
*/
|
||||
|
||||
#include "linux/CGroupUtils.h"
|
||||
|
||||
#include "XUtils.h"
|
||||
|
||||
|
||||
typedef struct StrBuf_state {
|
||||
char *buf;
|
||||
size_t size;
|
||||
size_t pos;
|
||||
} StrBuf_state;
|
||||
|
||||
typedef bool (*StrBuf_putc_t)(StrBuf_state* p, char c);
|
||||
|
||||
static bool StrBuf_putc_count(StrBuf_state* p, ATTR_UNUSED char c) {
|
||||
p->pos++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool StrBuf_putc_write(StrBuf_state* p, char c) {
|
||||
if (p->pos >= p->size)
|
||||
return false;
|
||||
|
||||
p->buf[p->pos] = c;
|
||||
p->pos++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) {
|
||||
for (; count; count--)
|
||||
if (!w(p, *s++))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool StrBuf_putsz(StrBuf_state* p, StrBuf_putc_t w, const char* s) {
|
||||
while (*s)
|
||||
if (!w(p, *s++))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Label_checkEqual(const char* labelStart, size_t labelLen, const char* expected) {
|
||||
return labelLen == strlen(expected) && String_startsWith(labelStart, expected);
|
||||
}
|
||||
|
||||
static bool Label_checkPrefix(const char* labelStart, size_t labelLen, const char* expected) {
|
||||
return labelLen > strlen(expected) && String_startsWith(labelStart, expected);
|
||||
}
|
||||
|
||||
static bool Label_checkSuffix(const char* labelStart, size_t labelLen, const char* expected) {
|
||||
return labelLen > strlen(expected) && String_startsWith(labelStart + labelLen - strlen(expected), expected);
|
||||
}
|
||||
|
||||
static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrBuf_putc_t w) {
|
||||
const char* str_slice_suffix = ".slice";
|
||||
const char* str_system_slice = "system.slice";
|
||||
const char* str_user_slice = "user.slice";
|
||||
const char* str_machine_slice = "machine.slice";
|
||||
const char* str_user_slice_prefix = "/user-";
|
||||
const char* str_system_slice_prefix = "/system-";
|
||||
|
||||
const char* str_lxc_monitor_legacy = "lxc.monitor";
|
||||
const char* str_lxc_payload_legacy = "lxc.payload";
|
||||
const char* str_lxc_monitor_prefix = "lxc.monitor.";
|
||||
const char* str_lxc_payload_prefix = "lxc.payload.";
|
||||
|
||||
const char* str_nspawn_scope_prefix = "machine-";
|
||||
const char* str_nspawn_monitor_label = "/supervisor";
|
||||
const char* str_nspawn_payload_label = "/payload";
|
||||
|
||||
const char* str_snap_scope_prefix = "snap.";
|
||||
|
||||
const char* str_service_suffix = ".service";
|
||||
const char* str_scope_suffix = ".scope";
|
||||
|
||||
while (*cgroup) {
|
||||
if ('/' == *cgroup) {
|
||||
while ('/' == *cgroup)
|
||||
cgroup++;
|
||||
|
||||
if (!w(s, '/'))
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* labelStart = cgroup;
|
||||
const char* nextSlash = strchrnul(labelStart, '/');
|
||||
const size_t labelLen = nextSlash - labelStart;
|
||||
|
||||
if (Label_checkEqual(labelStart, labelLen, str_system_slice)) {
|
||||
cgroup = nextSlash;
|
||||
|
||||
if (!StrBuf_putsz(s, w, "[S]"))
|
||||
return false;
|
||||
|
||||
if (String_startsWith(cgroup, str_system_slice_prefix)) {
|
||||
cgroup = strchrnul(cgroup + 1, '/');
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Label_checkEqual(labelStart, labelLen, str_machine_slice)) {
|
||||
cgroup = nextSlash;
|
||||
|
||||
if (!StrBuf_putsz(s, w, "[M]"))
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Label_checkEqual(labelStart, labelLen, str_user_slice)) {
|
||||
cgroup = nextSlash;
|
||||
|
||||
if (!StrBuf_putsz(s, w, "[U]"))
|
||||
return false;
|
||||
|
||||
if (!String_startsWith(cgroup, str_user_slice_prefix))
|
||||
continue;
|
||||
|
||||
const char* userSliceSlash = strchrnul(cgroup + strlen(str_user_slice_prefix), '/');
|
||||
const char* sliceSpec = userSliceSlash - strlen(str_slice_suffix);
|
||||
|
||||
if (!String_startsWith(sliceSpec, str_slice_suffix))
|
||||
continue;
|
||||
|
||||
const size_t sliceNameLen = sliceSpec - (cgroup + strlen(str_user_slice_prefix));
|
||||
|
||||
s->pos--;
|
||||
if (!w(s, ':'))
|
||||
return false;
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup + strlen(str_user_slice_prefix), sliceNameLen))
|
||||
return false;
|
||||
|
||||
if (!w(s, ']'))
|
||||
return false;
|
||||
|
||||
cgroup = userSliceSlash;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Label_checkSuffix(labelStart, labelLen, str_slice_suffix)) {
|
||||
const size_t sliceNameLen = labelLen - strlen(str_slice_suffix);
|
||||
|
||||
if (!w(s, '['))
|
||||
return false;
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup, sliceNameLen))
|
||||
return false;
|
||||
|
||||
if (!w(s, ']'))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) {
|
||||
const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix);
|
||||
|
||||
if (!StrBuf_putsz(s, w, "[lxc:"))
|
||||
return false;
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen))
|
||||
return false;
|
||||
|
||||
if (!w(s, ']'))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Label_checkPrefix(labelStart, labelLen, str_lxc_monitor_prefix)) {
|
||||
const size_t cgroupNameLen = labelLen - strlen(str_lxc_monitor_prefix);
|
||||
|
||||
if (!StrBuf_putsz(s, w, "[LXC:"))
|
||||
return false;
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_monitor_prefix), cgroupNameLen))
|
||||
return false;
|
||||
|
||||
if (!w(s, ']'))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// LXC legacy cgroup naming
|
||||
if (Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy) ||
|
||||
Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) {
|
||||
bool isMonitor = Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy);
|
||||
|
||||
labelStart = nextSlash;
|
||||
while (*labelStart == '/')
|
||||
labelStart++;
|
||||
|
||||
nextSlash = strchrnul(labelStart, '/');
|
||||
if (nextSlash - labelStart > 0) {
|
||||
if (!StrBuf_putsz(s, w, isMonitor ? "[LXC:" : "[lxc:"))
|
||||
return false;
|
||||
|
||||
if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart))
|
||||
return false;
|
||||
|
||||
if (!w(s, ']'))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
continue;
|
||||
}
|
||||
|
||||
labelStart = cgroup;
|
||||
nextSlash = labelStart + labelLen;
|
||||
}
|
||||
|
||||
if (Label_checkSuffix(labelStart, labelLen, str_service_suffix)) {
|
||||
const size_t serviceNameLen = labelLen - strlen(str_service_suffix);
|
||||
|
||||
if (String_startsWith(cgroup, "user@")) {
|
||||
cgroup = nextSlash;
|
||||
|
||||
while(*cgroup == '/')
|
||||
cgroup++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup, serviceNameLen))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) {
|
||||
const size_t scopeNameLen = labelLen - strlen(str_scope_suffix);
|
||||
|
||||
if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) {
|
||||
const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix);
|
||||
|
||||
const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label);
|
||||
|
||||
if (!StrBuf_putsz(s, w, is_monitor ? "[SNC:" : "[snc:"))
|
||||
return false;
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen))
|
||||
return false;
|
||||
|
||||
if (!w(s, ']'))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
if (String_startsWith(nextSlash, str_nspawn_monitor_label))
|
||||
cgroup += strlen(str_nspawn_monitor_label);
|
||||
else if (String_startsWith(nextSlash, str_nspawn_payload_label))
|
||||
cgroup += strlen(str_nspawn_payload_label);
|
||||
|
||||
continue;
|
||||
} else if(Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) {
|
||||
const char* nextDot = strchrnul(labelStart + strlen(str_snap_scope_prefix), '.');
|
||||
|
||||
if (!StrBuf_putsz(s, w, "!snap:"))
|
||||
return false;
|
||||
|
||||
if (nextDot >= labelStart + scopeNameLen) {
|
||||
nextDot = labelStart + scopeNameLen;
|
||||
}
|
||||
|
||||
if (!StrBuf_putsn(s, w, labelStart + strlen(str_snap_scope_prefix), nextDot - (labelStart + strlen(str_snap_scope_prefix))))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!w(s, '!'))
|
||||
return false;
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup, scopeNameLen))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default behavior: Copy the full label
|
||||
cgroup = labelStart;
|
||||
|
||||
if (!StrBuf_putsn(s, w, cgroup, labelLen))
|
||||
return false;
|
||||
|
||||
cgroup = nextSlash;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char* CGroup_filterName(const char *cgroup) {
|
||||
StrBuf_state s = {
|
||||
.buf = NULL,
|
||||
.size = 0,
|
||||
.pos = 0,
|
||||
};
|
||||
|
||||
if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_count)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s.buf = xCalloc(s.pos + 1, sizeof(char));
|
||||
s.size = s.pos;
|
||||
s.pos = 0;
|
||||
|
||||
if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_write)) {
|
||||
free(s.buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s.buf[s.size] = '\0';
|
||||
return s.buf;
|
||||
}
|
16
linux/CGroupUtils.h
Normal file
16
linux/CGroupUtils.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef HEADER_CGroupUtils
|
||||
#define HEADER_CGroupUtils
|
||||
/*
|
||||
htop - CGroupUtils.h
|
||||
(C) 2021 htop dev team
|
||||
Released under the GNU GPLv2+, see the COPYING file
|
||||
in the source distribution for its full text.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
char* CGroup_filterName(const char *cgroup);
|
||||
|
||||
#endif /* HEADER_CGroupUtils */
|
@ -56,11 +56,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
|
||||
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
|
||||
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
|
||||
@ -81,7 +81,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
|
||||
[IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
|
||||
[IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
|
||||
[CGROUP] = { .name = "CGROUP", .title = " CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, },
|
||||
[CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw)", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
|
||||
[CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed)", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },
|
||||
[OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },
|
||||
[IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, },
|
||||
#ifdef HAVE_DELAYACCT
|
||||
@ -93,7 +94,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
|
||||
[M_PSSWP] = { .name = "M_PSSWP", .title = " PSSWP ", .description = "shows proportional swap share of this mapping, unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },
|
||||
[CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, },
|
||||
[SECATTR] = { .name = "SECATTR", .title = " Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, },
|
||||
[SECATTR] = { .name = "SECATTR", .title = "Security Attribute", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, .autoWidth = true, },
|
||||
[PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process from /proc/[pid]/comm", .flags = 0, },
|
||||
[PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, },
|
||||
[CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
|
||||
@ -111,6 +112,7 @@ Process* LinuxProcess_new(const Settings* settings) {
|
||||
void Process_delete(Object* cast) {
|
||||
LinuxProcess* this = (LinuxProcess*) cast;
|
||||
Process_done((Process*)cast);
|
||||
free(this->cgroup_short);
|
||||
free(this->cgroup);
|
||||
#ifdef HAVE_OPENVZ
|
||||
free(this->ctid);
|
||||
@ -246,7 +248,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
|
||||
#ifdef HAVE_VSERVER
|
||||
case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break;
|
||||
#endif
|
||||
case CGROUP: xSnprintf(buffer, n, "%-10s ", lp->cgroup ? lp->cgroup : ""); break;
|
||||
case CGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CGROUP], Process_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : "N/A"); break;
|
||||
case CCGROUP: xSnprintf(buffer, n, "%-*.*s ", Process_fieldWidths[CCGROUP], Process_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break;
|
||||
case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break;
|
||||
case IO_PRIORITY: {
|
||||
int klass = IOPriority_class(lp->ioPriority);
|
||||
@ -267,9 +270,9 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_DELAYACCT
|
||||
case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, &attr); break;
|
||||
case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, &attr); break;
|
||||
case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, &attr); break;
|
||||
case PERCENT_CPU_DELAY: Process_printPercentage(lp->cpu_delay_percent, buffer, n, 4, &attr); break;
|
||||
case PERCENT_IO_DELAY: Process_printPercentage(lp->blkio_delay_percent, buffer, n, 4, &attr); break;
|
||||
case PERCENT_SWAP_DELAY: Process_printPercentage(lp->swapin_delay_percent, buffer, n, 4, &attr); break;
|
||||
#endif
|
||||
case CTXT:
|
||||
if (lp->ctxt_diff > 1000) {
|
||||
@ -277,7 +280,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces
|
||||
}
|
||||
xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff);
|
||||
break;
|
||||
case SECATTR: snprintf(buffer, n, "%-30s ", lp->secattr ? lp->secattr : "?"); break;
|
||||
case SECATTR: snprintf(buffer, n, "%-*.*s ", Process_fieldWidths[SECATTR], Process_fieldWidths[SECATTR], lp->secattr ? lp->secattr : "N/A"); break;
|
||||
case AUTOGROUP_ID:
|
||||
if (lp->autogroup_id != -1) {
|
||||
xSnprintf(buffer, n, "%4ld ", lp->autogroup_id);
|
||||
@ -370,6 +373,8 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce
|
||||
#endif
|
||||
case CGROUP:
|
||||
return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);
|
||||
case CCGROUP:
|
||||
return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short);
|
||||
case OOM:
|
||||
return SPACESHIP_NUMBER(p1->oom, p2->oom);
|
||||
#ifdef HAVE_DELAYACCT
|
||||
|
@ -48,6 +48,9 @@ typedef struct LinuxProcess_ {
|
||||
long m_drs;
|
||||
long m_lrs;
|
||||
|
||||
/* Process flags */
|
||||
unsigned long int flags;
|
||||
|
||||
/* Data read (in bytes) */
|
||||
unsigned long long io_rchar;
|
||||
|
||||
@ -86,6 +89,7 @@ typedef struct LinuxProcess_ {
|
||||
unsigned int vxid;
|
||||
#endif
|
||||
char* cgroup;
|
||||
char* cgroup_short;
|
||||
unsigned int oom;
|
||||
#ifdef HAVE_DELAYACCT
|
||||
unsigned long long int delay_read_time;
|
||||
|
@ -45,6 +45,7 @@ in the source distribution for its full text.
|
||||
#include "Process.h"
|
||||
#include "Settings.h"
|
||||
#include "XUtils.h"
|
||||
#include "linux/CGroupUtils.h"
|
||||
#include "linux/LinuxProcess.h"
|
||||
#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
|
||||
|
||||
@ -62,6 +63,10 @@ in the source distribution for its full text.
|
||||
#define O_PATH 010000000 // declare for ancient glibc versions
|
||||
#endif
|
||||
|
||||
/* Not exposed yet. Defined at include/linux/sched.h */
|
||||
#ifndef PF_KTHREAD
|
||||
#define PF_KTHREAD 0x00200000
|
||||
#endif
|
||||
|
||||
static long long btime = -1;
|
||||
|
||||
@ -169,21 +174,24 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) {
|
||||
LinuxProcessList* this = (LinuxProcessList*) super;
|
||||
unsigned int existing = 0, active = 0;
|
||||
|
||||
DIR* dir = opendir("/sys/devices/system/cpu");
|
||||
if (!dir) {
|
||||
this->cpuData = xReallocArrayZero(this->cpuData, super->existingCPUs ? (super->existingCPUs + 1) : 0, 2, sizeof(CPUData));
|
||||
// Initialize the cpuData array before anything else.
|
||||
if (!this->cpuData) {
|
||||
this->cpuData = xCalloc(2, sizeof(CPUData));
|
||||
this->cpuData[0].online = true; /* average is always "online" */
|
||||
this->cpuData[1].online = true;
|
||||
super->activeCPUs = 1;
|
||||
super->existingCPUs = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
DIR* dir = opendir("/sys/devices/system/cpu");
|
||||
if (!dir)
|
||||
return;
|
||||
|
||||
unsigned int currExisting = super->existingCPUs;
|
||||
|
||||
const struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_type != DT_DIR)
|
||||
if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
|
||||
continue;
|
||||
|
||||
if (!String_startsWith(entry->d_name, "cpu"))
|
||||
@ -228,6 +236,10 @@ static void LinuxProcessList_updateCPUcount(ProcessList* super) {
|
||||
|
||||
closedir(dir);
|
||||
|
||||
// return if no CPU is found
|
||||
if (existing < 1)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_SENSORS_SENSORS_H
|
||||
/* When started with offline CPUs, libsensors does not monitor those,
|
||||
* even when they become online. */
|
||||
@ -310,6 +322,22 @@ static inline unsigned long long LinuxProcessList_adjustTime(unsigned long long
|
||||
return t * 100 / jiffy;
|
||||
}
|
||||
|
||||
/* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */
|
||||
static inline ProcessState LinuxProcessList_getProcessState(char state) {
|
||||
switch (state) {
|
||||
case 'S': return SLEEPING;
|
||||
case 'X': return DEFUNCT;
|
||||
case 'Z': return ZOMBIE;
|
||||
case 't': return TRACED;
|
||||
case 'T': return STOPPED;
|
||||
case 'D': return UNINTERRUPTIBLE_WAIT;
|
||||
case 'R': return RUNNING;
|
||||
case 'P': return BLOCKED;
|
||||
case 'I': return IDLE;
|
||||
default: return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, char* command, size_t commLen) {
|
||||
LinuxProcess* lp = (LinuxProcess*) process;
|
||||
|
||||
@ -335,7 +363,7 @@ static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd,
|
||||
location = end + 2;
|
||||
|
||||
/* (3) state - %c */
|
||||
process->state = location[0];
|
||||
process->state = LinuxProcessList_getProcessState(location[0]);
|
||||
location += 2;
|
||||
|
||||
/* (4) ppid - %d */
|
||||
@ -358,8 +386,9 @@ static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd,
|
||||
process->tpgid = strtol(location, &location, 10);
|
||||
location += 1;
|
||||
|
||||
/* Skip (9) flags - %u */
|
||||
location = strchr(location, ' ') + 1;
|
||||
/* (9) flags - %u */
|
||||
lp->flags = strtoul(location, &location, 10);
|
||||
location += 1;
|
||||
|
||||
/* (10) minflt - %lu */
|
||||
process->minflt = strtoull(location, &location, 10);
|
||||
@ -471,6 +500,8 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc
|
||||
|
||||
unsigned long long last_read = process->io_read_bytes;
|
||||
unsigned long long last_write = process->io_write_bytes;
|
||||
unsigned long long time_delta = realtimeMs > process->io_last_scan_time_ms ? realtimeMs - process->io_last_scan_time_ms : 0;
|
||||
|
||||
char* buf = buffer;
|
||||
const char* line;
|
||||
while ((line = strsep(&buf, "\n")) != NULL) {
|
||||
@ -480,7 +511,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc
|
||||
process->io_rchar = strtoull(line + 7, NULL, 10);
|
||||
} else if (String_startsWith(line + 1, "ead_bytes: ")) {
|
||||
process->io_read_bytes = strtoull(line + 12, NULL, 10);
|
||||
process->io_rate_read_bps = (process->io_read_bytes - last_read) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms);
|
||||
process->io_rate_read_bps = time_delta ? (process->io_read_bytes - last_read) * /*ms to s*/1000. / time_delta : NAN;
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
@ -488,7 +519,7 @@ static void LinuxProcessList_readIoFile(LinuxProcess* process, openat_arg_t proc
|
||||
process->io_wchar = strtoull(line + 7, NULL, 10);
|
||||
} else if (String_startsWith(line + 1, "rite_bytes: ")) {
|
||||
process->io_write_bytes = strtoull(line + 13, NULL, 10);
|
||||
process->io_rate_write_bps = (process->io_write_bytes - last_write) * /*ms to s*/1000 / (realtimeMs - process->io_last_scan_time_ms);
|
||||
process->io_rate_write_bps = time_delta ? (process->io_write_bytes - last_write) * /*ms to s*/1000. / time_delta : NAN;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
@ -654,6 +685,11 @@ static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd
|
||||
if (String_startsWith(readptr, "/memfd:"))
|
||||
continue;
|
||||
|
||||
/* Virtualbox maps /dev/zero for memory allocation. That results in
|
||||
* false positive, so ignore. */
|
||||
if (String_eq(readptr, "/dev/zero (deleted)\n"))
|
||||
continue;
|
||||
|
||||
if (strstr(readptr, " (deleted)\n")) {
|
||||
proc->usesDeletedLib = true;
|
||||
if (!calcSize)
|
||||
@ -834,6 +870,10 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t
|
||||
free(process->cgroup);
|
||||
process->cgroup = NULL;
|
||||
}
|
||||
if (process->cgroup_short) {
|
||||
free(process->cgroup_short);
|
||||
process->cgroup_short = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
char output[PROC_LINE_LENGTH + 1];
|
||||
@ -846,9 +886,16 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t
|
||||
if (!ok)
|
||||
break;
|
||||
|
||||
char* group = strchr(buffer, ':');
|
||||
if (!group)
|
||||
break;
|
||||
char* group = buffer;
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
group = strchrnul(group, ':');
|
||||
if (!*group)
|
||||
break;
|
||||
group++;
|
||||
}
|
||||
|
||||
char* eol = strchrnul(group, '\n');
|
||||
*eol = '\0';
|
||||
|
||||
if (at != output) {
|
||||
*at = ';';
|
||||
@ -859,7 +906,33 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t
|
||||
left -= wrote;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
bool changed = !process->cgroup || !String_eq(process->cgroup, output);
|
||||
|
||||
Process_updateFieldWidth(CGROUP, strlen(output));
|
||||
free_and_xStrdup(&process->cgroup, output);
|
||||
|
||||
if (!changed) {
|
||||
if(process->cgroup_short) {
|
||||
Process_updateFieldWidth(CCGROUP, strlen(process->cgroup_short));
|
||||
} else {
|
||||
//CCGROUP is alias to normal CGROUP if shortening fails
|
||||
Process_updateFieldWidth(CCGROUP, strlen(process->cgroup));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
char* cgroup_short = CGroup_filterName(process->cgroup);
|
||||
if (cgroup_short) {
|
||||
Process_updateFieldWidth(CCGROUP, strlen(cgroup_short));
|
||||
free_and_xStrdup(&process->cgroup_short, cgroup_short);
|
||||
free(cgroup_short);
|
||||
} else {
|
||||
//CCGROUP is alias to normal CGROUP if shortening fails
|
||||
Process_updateFieldWidth(CCGROUP, strlen(process->cgroup));
|
||||
free(process->cgroup_short);
|
||||
process->cgroup_short = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_VSERVER
|
||||
@ -974,6 +1047,9 @@ static void LinuxProcessList_readSecattrData(LinuxProcess* process, openat_arg_t
|
||||
if (newline) {
|
||||
*newline = '\0';
|
||||
}
|
||||
|
||||
Process_updateFieldWidth(SECATTR, strlen(buffer));
|
||||
|
||||
if (process->secattr && String_eq(process->secattr, buffer)) {
|
||||
return;
|
||||
}
|
||||
@ -1089,17 +1165,9 @@ delayacct_failure:
|
||||
static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t procFd) {
|
||||
char command[4096 + 1]; // max cmdline length on Linux
|
||||
ssize_t amtRead = xReadfileat(procFd, "cmdline", command, sizeof(command));
|
||||
if (amtRead < 0)
|
||||
if (amtRead <= 0)
|
||||
return false;
|
||||
|
||||
if (amtRead == 0) {
|
||||
if (process->state != 'Z') {
|
||||
process->isKernelThread = true;
|
||||
}
|
||||
Process_updateCmdline(process, NULL, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int tokenEnd = 0;
|
||||
int tokenStart = 0;
|
||||
int lastChar = 0;
|
||||
@ -1325,6 +1393,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
|
||||
ProcessList* pl = (ProcessList*) this;
|
||||
const struct dirent* entry;
|
||||
const Settings* settings = pl->settings;
|
||||
const ScreenSettings* ss = settings->ss;
|
||||
|
||||
#ifdef HAVE_OPENAT
|
||||
int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
|
||||
@ -1418,7 +1487,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
|
||||
continue;
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_IO)
|
||||
if (ss->flags & PROCESS_FLAG_IO)
|
||||
LinuxProcessList_readIoFile(lp, procFd, pl->realtimeMs);
|
||||
|
||||
if (!LinuxProcessList_readStatmFile(lp, procFd))
|
||||
@ -1427,8 +1496,9 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
|
||||
{
|
||||
bool prev = proc->usesDeletedLib;
|
||||
|
||||
if ((settings->flags & PROCESS_FLAG_LINUX_LRS_FIX) ||
|
||||
(settings->highlightDeletedExe && !proc->procExeDeleted && !proc->isKernelThread && !proc->isUserlandThread)) {
|
||||
if (!proc->isKernelThread && !proc->isUserlandThread &&
|
||||
((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted))) {
|
||||
|
||||
// Check if we really should recalculate the M_LRS value for this process
|
||||
uint64_t passedTimeInMs = pl->realtimeMs - lp->last_mlrs_calctime;
|
||||
|
||||
@ -1436,17 +1506,18 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
|
||||
|
||||
if (passedTimeInMs > recheck) {
|
||||
lp->last_mlrs_calctime = pl->realtimeMs;
|
||||
LinuxProcessList_readMaps(lp, procFd, settings->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe);
|
||||
LinuxProcessList_readMaps(lp, procFd, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe);
|
||||
}
|
||||
} else {
|
||||
/* Copy from process structure in threads and reset if setting got disabled */
|
||||
proc->usesDeletedLib = (proc->isUserlandThread && parent) ? parent->usesDeletedLib : false;
|
||||
lp->m_lrs = (proc->isUserlandThread && parent) ? ((const LinuxProcess*)parent)->m_lrs : 0;
|
||||
}
|
||||
|
||||
proc->mergedCommand.exeChanged |= prev ^ proc->usesDeletedLib;
|
||||
}
|
||||
|
||||
if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
|
||||
if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {
|
||||
if (!parent) {
|
||||
// Read smaps file of each process only every second pass to improve performance
|
||||
static int smaps_flag = 0;
|
||||
@ -1467,12 +1538,16 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
|
||||
if (! LinuxProcessList_readStatFile(proc, procFd, statCommand, sizeof(statCommand)))
|
||||
goto errorReadingProcess;
|
||||
|
||||
if (lp->flags & PF_KTHREAD) {
|
||||
proc->isKernelThread = true;
|
||||
}
|
||||
|
||||
if (tty_nr != proc->tty_nr && this->ttyDrivers) {
|
||||
free(proc->tty_name);
|
||||
proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr);
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_IOPRIO) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) {
|
||||
LinuxProcess_updateIOPriority(lp);
|
||||
}
|
||||
|
||||
@ -1480,6 +1555,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
|
||||
float percent_cpu = (period < 1E-6) ? 0.0F : ((lp->utime + lp->stime - lasttimes) / period * 100.0);
|
||||
proc->percent_cpu = CLAMP(percent_cpu, 0.0F, activeCPUs * 100.0F);
|
||||
proc->percent_mem = proc->m_resident / (double)(pl->totalMem) * 100.0;
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
if (! LinuxProcessList_updateUser(pl, proc, procFd))
|
||||
goto errorReadingProcess;
|
||||
@ -1487,64 +1563,68 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
|
||||
if (!preExisting) {
|
||||
|
||||
#ifdef HAVE_OPENVZ
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_OPENVZ) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) {
|
||||
LinuxProcessList_readOpenVZData(lp, procFd);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_VSERVER
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_VSERVER) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_VSERVER) {
|
||||
LinuxProcessList_readVServerData(lp, procFd);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
|
||||
goto errorReadingProcess;
|
||||
if (proc->isKernelThread) {
|
||||
Process_updateCmdline(proc, NULL, 0, 0);
|
||||
} else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) {
|
||||
Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
|
||||
}
|
||||
|
||||
Process_fillStarttimeBuffer(proc);
|
||||
|
||||
ProcessList_add(pl, proc);
|
||||
} else {
|
||||
if (settings->updateProcessNames && proc->state != 'Z') {
|
||||
if (! LinuxProcessList_readCmdlineFile(proc, procFd)) {
|
||||
goto errorReadingProcess;
|
||||
if (settings->updateProcessNames && proc->state != ZOMBIE) {
|
||||
if (proc->isKernelThread) {
|
||||
Process_updateCmdline(proc, NULL, 0, 0);
|
||||
} else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) {
|
||||
Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_DELAYACCT
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) {
|
||||
LinuxProcessList_readDelayAcctData(this, lp);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_CGROUP) {
|
||||
LinuxProcessList_readCGroupFile(lp, procFd);
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_OOM) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_OOM) {
|
||||
LinuxProcessList_readOomData(lp, procFd);
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_CTXT) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_CTXT) {
|
||||
LinuxProcessList_readCtxtData(lp, procFd);
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_SECATTR) {
|
||||
if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) {
|
||||
LinuxProcessList_readSecattrData(lp, procFd);
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_CWD) {
|
||||
if (ss->flags & PROCESS_FLAG_CWD) {
|
||||
LinuxProcessList_readCwd(lp, procFd);
|
||||
}
|
||||
|
||||
if ((settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
|
||||
if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {
|
||||
LinuxProcessList_readAutogroup(lp, procFd);
|
||||
}
|
||||
|
||||
if (!proc->cmdline && statCommand[0] &&
|
||||
(proc->state == 'Z' || Process_isKernelThread(proc) || settings->showThreadNames)) {
|
||||
(proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) {
|
||||
Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));
|
||||
}
|
||||
|
||||
@ -1941,7 +2021,7 @@ static inline double LinuxProcessList_scanCPUTime(ProcessList* super) {
|
||||
return period;
|
||||
}
|
||||
|
||||
static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) {
|
||||
static int scanCPUFrequencyFromSysCPUFreq(LinuxProcessList* this) {
|
||||
unsigned int existingCPUs = this->super.existingCPUs;
|
||||
int numCPUsWithFrequency = 0;
|
||||
unsigned long totalFrequency = 0;
|
||||
@ -2004,7 +2084,7 @@ static int scanCPUFreqencyFromSysCPUFreq(LinuxProcessList* this) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void scanCPUFreqencyFromCPUinfo(LinuxProcessList* this) {
|
||||
static void scanCPUFrequencyFromCPUinfo(LinuxProcessList* this) {
|
||||
FILE* file = fopen(PROCCPUINFOFILE, "r");
|
||||
if (file == NULL)
|
||||
return;
|
||||
@ -2061,11 +2141,11 @@ static void LinuxProcessList_scanCPUFrequency(LinuxProcessList* this) {
|
||||
this->cpuData[i].frequency = NAN;
|
||||
}
|
||||
|
||||
if (scanCPUFreqencyFromSysCPUFreq(this) == 0) {
|
||||
if (scanCPUFrequencyFromSysCPUFreq(this) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
scanCPUFreqencyFromCPUinfo(this);
|
||||
scanCPUFrequencyFromCPUinfo(this);
|
||||
}
|
||||
|
||||
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
@ -2093,7 +2173,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) {
|
||||
if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) {
|
||||
// Refer to sched(7) 'autogroup feature' section
|
||||
// The kernel feature can be enabled/disabled through procfs at
|
||||
// any time, so check for it at the start of each sample - only
|
||||
|
328
linux/Platform.c
328
linux/Platform.c
@ -70,6 +70,10 @@ in the source distribution for its full text.
|
||||
#include "LibSensors.h"
|
||||
#endif
|
||||
|
||||
#ifndef O_PATH
|
||||
#define O_PATH 010000000 // declare for ancient glibc versions
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef HAVE_LIBCAP
|
||||
enum CapMode {
|
||||
@ -79,7 +83,22 @@ enum CapMode {
|
||||
};
|
||||
#endif
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
bool Running_containerized = false;
|
||||
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
{
|
||||
.name = "I/O",
|
||||
.columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command",
|
||||
.sortKey = "IO_RATE",
|
||||
},
|
||||
};
|
||||
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
const SignalItem Platform_signals[] = {
|
||||
{ .name = " 0 Cancel", .number = 0 },
|
||||
@ -338,7 +357,7 @@ void Platform_setMemoryValues(Meter* this) {
|
||||
this->values[3] = pl->cachedMem;
|
||||
this->values[4] = pl->availableMem;
|
||||
|
||||
if (lpl->zfs.enabled != 0) {
|
||||
if (lpl->zfs.enabled != 0 && !Running_containerized) {
|
||||
this->values[0] -= lpl->zfs.size;
|
||||
this->values[3] += lpl->zfs.size;
|
||||
}
|
||||
@ -611,133 +630,85 @@ bool Platform_getNetworkIO(NetworkIOData* data) {
|
||||
|
||||
// Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
|
||||
|
||||
#define MAX_BATTERIES 64
|
||||
#define PROC_BATTERY_DIR PROCDIR "/acpi/battery"
|
||||
#define PROC_POWERSUPPLY_DIR PROCDIR "/acpi/ac_adapter"
|
||||
#define PROC_POWERSUPPLY_ACSTATE_FILE PROC_POWERSUPPLY_DIR "/AC/state"
|
||||
#define SYS_POWERSUPPLY_DIR "/sys/class/power_supply"
|
||||
|
||||
// ----------------------------------------
|
||||
// READ FROM /proc
|
||||
// ----------------------------------------
|
||||
|
||||
static unsigned long int parseBatInfo(const char* fileName, const unsigned short int lineNum, const unsigned short int wordNum) {
|
||||
const char batteryPath[] = PROC_BATTERY_DIR;
|
||||
DIR* batteryDir = opendir(batteryPath);
|
||||
static double Platform_Battery_getProcBatInfo(void) {
|
||||
DIR* batteryDir = opendir(PROC_BATTERY_DIR);
|
||||
if (!batteryDir)
|
||||
return 0;
|
||||
return NAN;
|
||||
|
||||
char* batteries[MAX_BATTERIES];
|
||||
unsigned int nBatteries = 0;
|
||||
memset(batteries, 0, MAX_BATTERIES * sizeof(char*));
|
||||
|
||||
while (nBatteries < MAX_BATTERIES) {
|
||||
const struct dirent* dirEntry = readdir(batteryDir);
|
||||
if (!dirEntry)
|
||||
break;
|
||||
uint64_t totalFull = 0;
|
||||
uint64_t totalRemain = 0;
|
||||
|
||||
struct dirent* dirEntry = NULL;
|
||||
while ((dirEntry = readdir(batteryDir))) {
|
||||
const char* entryName = dirEntry->d_name;
|
||||
if (!String_startsWith(entryName, "BAT"))
|
||||
continue;
|
||||
|
||||
batteries[nBatteries] = xStrdup(entryName);
|
||||
nBatteries++;
|
||||
}
|
||||
closedir(batteryDir);
|
||||
char filePath[256];
|
||||
char bufInfo[1024] = {0};
|
||||
xSnprintf(filePath, sizeof(filePath), "%s/%s/info", PROC_BATTERY_DIR, entryName);
|
||||
ssize_t r = xReadfile(filePath, bufInfo, sizeof(bufInfo));
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
unsigned long int total = 0;
|
||||
for (unsigned int i = 0; i < nBatteries; i++) {
|
||||
char infoPath[30];
|
||||
xSnprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName);
|
||||
char bufState[1024] = {0};
|
||||
xSnprintf(filePath, sizeof(filePath), "%s/%s/state", PROC_BATTERY_DIR, entryName);
|
||||
r = xReadfile(filePath, bufState, sizeof(bufState));
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
FILE* file = fopen(infoPath, "r");
|
||||
if (!file)
|
||||
break;
|
||||
const char* line;
|
||||
|
||||
char* line = NULL;
|
||||
for (unsigned short int j = 0; j < lineNum; j++) {
|
||||
free(line);
|
||||
line = String_readLine(file);
|
||||
if (!line)
|
||||
//Getting total charge for all batteries
|
||||
char* buf = bufInfo;
|
||||
while ((line = strsep(&buf, "\n")) != NULL) {
|
||||
char field[100] = {0};
|
||||
int val = 0;
|
||||
if (2 != sscanf(line, "%99[^:]:%d", field, &val))
|
||||
continue;
|
||||
|
||||
if (String_eq(field, "last full capacity")) {
|
||||
totalFull += val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
//Getting remaining charge for all batteries
|
||||
buf = bufState;
|
||||
while ((line = strsep(&buf, "\n")) != NULL) {
|
||||
char field[100] = {0};
|
||||
int val = 0;
|
||||
if (2 != sscanf(line, "%99[^:]:%d", field, &val))
|
||||
continue;
|
||||
|
||||
if (!line)
|
||||
break;
|
||||
|
||||
char* foundNumStr = String_getToken(line, wordNum);
|
||||
const unsigned long int foundNum = atoi(foundNumStr);
|
||||
free(foundNumStr);
|
||||
free(line);
|
||||
|
||||
total += foundNum;
|
||||
if (String_eq(field, "remaining capacity")) {
|
||||
totalRemain += val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < nBatteries; i++)
|
||||
free(batteries[i]);
|
||||
closedir(batteryDir);
|
||||
|
||||
return total;
|
||||
return totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;
|
||||
}
|
||||
|
||||
static ACPresence procAcpiCheck(void) {
|
||||
ACPresence isOn = AC_ERROR;
|
||||
const char* power_supplyPath = PROC_POWERSUPPLY_DIR;
|
||||
DIR* dir = opendir(power_supplyPath);
|
||||
if (!dir)
|
||||
char buffer[1024] = {0};
|
||||
ssize_t r = xReadfile(PROC_POWERSUPPLY_ACSTATE_FILE, buffer, sizeof(buffer));
|
||||
if (r < 1)
|
||||
return AC_ERROR;
|
||||
|
||||
for (;;) {
|
||||
const struct dirent* dirEntry = readdir(dir);
|
||||
if (!dirEntry)
|
||||
break;
|
||||
|
||||
const char* entryName = dirEntry->d_name;
|
||||
|
||||
if (entryName[0] != 'A')
|
||||
continue;
|
||||
|
||||
char statePath[256];
|
||||
xSnprintf(statePath, sizeof(statePath), "%s/%s/state", power_supplyPath, entryName);
|
||||
FILE* file = fopen(statePath, "r");
|
||||
if (!file) {
|
||||
isOn = AC_ERROR;
|
||||
continue;
|
||||
}
|
||||
char* line = String_readLine(file);
|
||||
|
||||
fclose(file);
|
||||
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
char* isOnline = String_getToken(line, 2);
|
||||
free(line);
|
||||
|
||||
if (String_eq(isOnline, "on-line"))
|
||||
isOn = AC_PRESENT;
|
||||
else
|
||||
isOn = AC_ABSENT;
|
||||
free(isOnline);
|
||||
if (isOn == AC_PRESENT)
|
||||
break;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return isOn;
|
||||
}
|
||||
|
||||
static double Platform_Battery_getProcBatInfo(void) {
|
||||
const unsigned long int totalFull = parseBatInfo("info", 3, 4);
|
||||
if (totalFull == 0)
|
||||
return NAN;
|
||||
|
||||
const unsigned long int totalRemain = parseBatInfo("state", 5, 3);
|
||||
if (totalRemain == 0)
|
||||
return NAN;
|
||||
|
||||
return totalRemain * 100.0 / (double) totalFull;
|
||||
return String_eq(buffer, "on-line") ? AC_PRESENT : AC_ABSENT;
|
||||
}
|
||||
|
||||
static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
|
||||
@ -750,7 +721,6 @@ static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {
|
||||
// ----------------------------------------
|
||||
|
||||
static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
|
||||
|
||||
*percent = NAN;
|
||||
*isOnAC = AC_ERROR;
|
||||
|
||||
@ -758,68 +728,81 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
|
||||
if (!dir)
|
||||
return;
|
||||
|
||||
unsigned long int totalFull = 0;
|
||||
unsigned long int totalRemain = 0;
|
||||
|
||||
for (;;) {
|
||||
const struct dirent* dirEntry = readdir(dir);
|
||||
if (!dirEntry)
|
||||
break;
|
||||
uint64_t totalFull = 0;
|
||||
uint64_t totalRemain = 0;
|
||||
|
||||
const struct dirent* dirEntry;
|
||||
while ((dirEntry = readdir(dir))) {
|
||||
const char* entryName = dirEntry->d_name;
|
||||
char filePath[256];
|
||||
|
||||
xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/type", entryName);
|
||||
|
||||
char type[8];
|
||||
ssize_t r = xReadfile(filePath, type, sizeof(type));
|
||||
if (r < 3)
|
||||
#ifdef HAVE_OPENAT
|
||||
int entryFd = openat(dirfd(dir), entryName, O_DIRECTORY | O_PATH);
|
||||
if (entryFd < 0)
|
||||
continue;
|
||||
#else
|
||||
char entryFd[4096];
|
||||
xSnprintf(entryFd, sizeof(entryFd), SYS_POWERSUPPLY_DIR "/%s", entryName);
|
||||
#endif
|
||||
|
||||
if (type[0] == 'B' && type[1] == 'a' && type[2] == 't') {
|
||||
xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName);
|
||||
enum { AC, BAT } type;
|
||||
if (String_startsWith(entryName, "BAT")) {
|
||||
type = BAT;
|
||||
} else if (String_startsWith(entryName, "AC")) {
|
||||
type = AC;
|
||||
} else {
|
||||
char buffer[32];
|
||||
ssize_t ret = xReadfileat(entryFd, "type", buffer, sizeof(buffer));
|
||||
if (ret <= 0)
|
||||
goto next;
|
||||
|
||||
/* drop optional trailing newlines */
|
||||
for (char* buf = &buffer[(size_t)ret - 1]; *buf == '\n'; buf--)
|
||||
*buf = '\0';
|
||||
|
||||
if (String_eq(buffer, "Battery"))
|
||||
type = BAT;
|
||||
else if (String_eq(buffer, "Mains"))
|
||||
type = AC;
|
||||
else
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (type == BAT) {
|
||||
char buffer[1024];
|
||||
r = xReadfile(filePath, buffer, sizeof(buffer));
|
||||
if (r < 0) {
|
||||
closedir(dir);
|
||||
return;
|
||||
}
|
||||
ssize_t r = xReadfileat(entryFd, "uevent", buffer, sizeof(buffer));
|
||||
if (r < 0)
|
||||
goto next;
|
||||
|
||||
char* buf = buffer;
|
||||
const char* line;
|
||||
bool full = false;
|
||||
bool now = false;
|
||||
int fullSize = 0;
|
||||
|
||||
double fullCharge = 0;
|
||||
double capacityLevel = NAN;
|
||||
const char* line;
|
||||
|
||||
#define match(str,prefix) \
|
||||
(String_startsWith(str,prefix) ? (str) + strlen(prefix) : NULL)
|
||||
|
||||
char* buf = buffer;
|
||||
while ((line = strsep(&buf, "\n")) != NULL) {
|
||||
const char* ps = match(line, "POWER_SUPPLY_");
|
||||
if (!ps)
|
||||
char field[100] = {0};
|
||||
int val = 0;
|
||||
if (2 != sscanf(line, "POWER_SUPPLY_%99[^=]=%d", field, &val))
|
||||
continue;
|
||||
const char* capacity = match(ps, "CAPACITY=");
|
||||
if (capacity)
|
||||
capacityLevel = atoi(capacity) / 100.0;
|
||||
const char* energy = match(ps, "ENERGY_");
|
||||
if (!energy)
|
||||
energy = match(ps, "CHARGE_");
|
||||
if (!energy)
|
||||
|
||||
if (String_eq(field, "CAPACITY")) {
|
||||
capacityLevel = val / 100.0;
|
||||
continue;
|
||||
const char* value = (!full) ? match(energy, "FULL=") : NULL;
|
||||
if (value) {
|
||||
fullSize = atoi(value);
|
||||
totalFull += fullSize;
|
||||
}
|
||||
|
||||
if (String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL")) {
|
||||
fullCharge = val;
|
||||
totalFull += fullCharge;
|
||||
full = true;
|
||||
if (now)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
value = (!now) ? match(energy, "NOW=") : NULL;
|
||||
if (value) {
|
||||
totalRemain += atoi(value);
|
||||
|
||||
if (String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW")) {
|
||||
totalRemain += val;
|
||||
now = true;
|
||||
if (full)
|
||||
break;
|
||||
@ -827,23 +810,18 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
|
||||
}
|
||||
}
|
||||
|
||||
#undef match
|
||||
|
||||
if (!now && full && !isnan(capacityLevel))
|
||||
totalRemain += (capacityLevel * fullSize);
|
||||
totalRemain += capacityLevel * fullCharge;
|
||||
|
||||
} else if (entryName[0] == 'A') {
|
||||
} else if (type == AC) {
|
||||
if (*isOnAC != AC_ERROR)
|
||||
continue;
|
||||
|
||||
xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/online", entryName);
|
||||
goto next;
|
||||
|
||||
char buffer[2];
|
||||
|
||||
r = xReadfile(filePath, buffer, sizeof(buffer));
|
||||
ssize_t r = xReadfileat(entryFd, "online", buffer, sizeof(buffer));
|
||||
if (r < 1) {
|
||||
closedir(dir);
|
||||
return;
|
||||
*isOnAC = AC_ERROR;
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (buffer[0] == '0')
|
||||
@ -851,7 +829,11 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {
|
||||
else if (buffer[0] == '1')
|
||||
*isOnAC = AC_PRESENT;
|
||||
}
|
||||
|
||||
next:
|
||||
Compat_openatArgClose(entryFd);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
*percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;
|
||||
@ -901,7 +883,7 @@ void Platform_longOptionsUsage(const char* name)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Platform_getLongOption(int opt, int argc, char** argv) {
|
||||
CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {
|
||||
#ifndef HAVE_LIBCAP
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
@ -924,16 +906,16 @@ bool Platform_getLongOption(int opt, int argc, char** argv) {
|
||||
Platform_capabilitiesMode = CAP_MODE_STRICT;
|
||||
} else {
|
||||
fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode);
|
||||
exit(1);
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
return true;
|
||||
return STATUS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBCAP
|
||||
@ -1022,20 +1004,46 @@ static int dropCapabilities(enum CapMode mode) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
#ifdef HAVE_LIBCAP
|
||||
if (dropCapabilities(Platform_capabilitiesMode) < 0)
|
||||
exit(1);
|
||||
return false;
|
||||
#endif
|
||||
|
||||
if (access(PROCDIR, R_OK) != 0) {
|
||||
fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
|
||||
exit(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SENSORS_SENSORS_H
|
||||
LibSensors_init();
|
||||
#endif
|
||||
|
||||
char target[PATH_MAX];
|
||||
ssize_t ret = readlink(PROCDIR "/self/ns/pid", target, sizeof(target) - 1);
|
||||
if (ret > 0) {
|
||||
target[ret] = '\0';
|
||||
|
||||
if (!String_eq("pid:[4026531836]", target)) { // magic constant PROC_PID_INIT_INO from include/linux/proc_ns.h#L46
|
||||
Running_containerized = true;
|
||||
return true; // early return
|
||||
}
|
||||
}
|
||||
|
||||
FILE* fd = fopen(PROCDIR "/1/mounts", "r");
|
||||
if (fd) {
|
||||
char lineBuffer[256];
|
||||
while (fgets(lineBuffer, sizeof(lineBuffer), fd)) {
|
||||
// detect lxc or overlayfs and guess that this means we are running containerized
|
||||
if (String_startsWith(lineBuffer, "lxcfs ") || String_startsWith(lineBuffer, "overlay ")) {
|
||||
Running_containerized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fd);
|
||||
} // if (fd)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_done(void) {
|
||||
|
@ -27,6 +27,7 @@ in the source distribution for its full text.
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "RichString.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "generic/hostname.h"
|
||||
#include "generic/uname.h"
|
||||
@ -37,7 +38,9 @@ in the source distribution for its full text.
|
||||
#endif
|
||||
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
extern const SignalItem Platform_signals[];
|
||||
|
||||
@ -45,8 +48,7 @@ extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
|
||||
bool Platform_init(void);
|
||||
void Platform_done(void);
|
||||
|
||||
void Platform_setBindings(Htop_Action* keys);
|
||||
@ -100,7 +102,7 @@ static inline void Platform_getRelease(char** string) {
|
||||
|
||||
void Platform_longOptionsUsage(const char* name);
|
||||
|
||||
bool Platform_getLongOption(int opt, int argc, char** argv);
|
||||
CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
Generic_gettime_realtime(tv, msec);
|
||||
|
@ -45,6 +45,7 @@ in the source distribution for its full text.
|
||||
SECATTR = 123, \
|
||||
AUTOGROUP_ID = 127, \
|
||||
AUTOGROUP_NICE = 128, \
|
||||
CCGROUP = 129, \
|
||||
// End of list
|
||||
|
||||
|
||||
|
@ -138,7 +138,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
},
|
||||
[ST_UID] = {
|
||||
.name = "ST_UID",
|
||||
.title = " UID ",
|
||||
.title = "UID",
|
||||
.description = "User ID of the process owner",
|
||||
.flags = 0,
|
||||
},
|
||||
@ -148,6 +148,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
.description = "Percentage of the CPU time the process used in the last sampling",
|
||||
.flags = 0,
|
||||
.defaultSortDesc = true,
|
||||
.autoWidth = true,
|
||||
},
|
||||
[PERCENT_NORM_CPU] = {
|
||||
.name = "PERCENT_NORM_CPU",
|
||||
@ -155,6 +156,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
.description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)",
|
||||
.flags = 0,
|
||||
.defaultSortDesc = true,
|
||||
.autoWidth = true,
|
||||
},
|
||||
[PERCENT_MEM] = {
|
||||
.name = "PERCENT_MEM",
|
||||
@ -165,7 +167,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
},
|
||||
[USER] = {
|
||||
.name = "USER",
|
||||
.title = "USER ",
|
||||
.title = "USER ",
|
||||
.description = "Username of the process owner (or user ID if name cannot be determined)",
|
||||
.flags = 0,
|
||||
},
|
||||
@ -211,7 +213,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
};
|
||||
|
||||
Process* NetBSDProcess_new(const Settings* settings) {
|
||||
NetBSDProcess* this = xCalloc(sizeof(NetBSDProcess), 1);
|
||||
NetBSDProcess* this = xCalloc(1, sizeof(NetBSDProcess));
|
||||
Object_setClass(this, Class(NetBSDProcess));
|
||||
Process_init(&this->super, settings);
|
||||
return &this->super;
|
||||
|
@ -307,7 +307,7 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) {
|
||||
}
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_CWD) {
|
||||
if (settings->ss->flags & PROCESS_FLAG_CWD) {
|
||||
NetBSDProcessList_updateCwd(kproc, proc);
|
||||
}
|
||||
|
||||
@ -318,8 +318,11 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) {
|
||||
|
||||
proc->m_virt = kproc->p_vm_vsize;
|
||||
proc->m_resident = kproc->p_vm_rssize;
|
||||
|
||||
proc->percent_mem = (proc->m_resident * pageSizeKB) / (double)(this->super.totalMem) * 100.0;
|
||||
proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0, this->super.activeCPUs * 100.0);
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
proc->nlwp = kproc->p_nlwps;
|
||||
proc->nice = kproc->p_nice - 20;
|
||||
proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
|
||||
@ -331,31 +334,33 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) {
|
||||
int nlwps = 0;
|
||||
const struct kinfo_lwp* klwps = kvm_getlwps(this->kd, kproc->p_pid, kproc->p_paddr, sizeof(struct kinfo_lwp), &nlwps);
|
||||
|
||||
/* TODO: According to the link below, SDYING should be a regarded state */
|
||||
/* Taken from: https://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/sys/proc.h */
|
||||
switch (kproc->p_realstat) {
|
||||
case SIDL: proc->state = 'I'; break;
|
||||
case SIDL: proc->state = IDLE; break;
|
||||
case SACTIVE:
|
||||
// We only consider the first LWP with a one of the below states.
|
||||
for (int j = 0; j < nlwps; j++) {
|
||||
if (klwps) {
|
||||
switch (klwps[j].l_stat) {
|
||||
case LSONPROC: proc->state = 'P'; break;
|
||||
case LSRUN: proc->state = 'R'; break;
|
||||
case LSSLEEP: proc->state = 'S'; break;
|
||||
case LSSTOP: proc->state = 'T'; break;
|
||||
default: proc->state = '?';
|
||||
case LSONPROC: proc->state = RUNNING; break;
|
||||
case LSRUN: proc->state = RUNNABLE; break;
|
||||
case LSSLEEP: proc->state = SLEEPING; break;
|
||||
case LSSTOP: proc->state = STOPPED; break;
|
||||
default: proc->state = UNKNOWN;
|
||||
}
|
||||
if (proc->state != '?')
|
||||
if (proc->state != UNKNOWN)
|
||||
break;
|
||||
} else {
|
||||
proc->state = '?';
|
||||
proc->state = UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SSTOP: proc->state = 'T'; break;
|
||||
case SZOMB: proc->state = 'Z'; break;
|
||||
case SDEAD: proc->state = 'D'; break;
|
||||
default: proc->state = '?';
|
||||
case SSTOP: proc->state = STOPPED; break;
|
||||
case SZOMB: proc->state = ZOMBIE; break;
|
||||
case SDEAD: proc->state = DEFUNCT; break;
|
||||
default: proc->state = UNKNOWN;
|
||||
}
|
||||
|
||||
if (Process_isKernelThread(proc)) {
|
||||
@ -365,8 +370,7 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) {
|
||||
}
|
||||
|
||||
this->super.totalTasks++;
|
||||
// SRUN ('R') means runnable, not running
|
||||
if (proc->state == 'P') {
|
||||
if (proc->state == RUNNING) {
|
||||
this->super.runningTasks++;
|
||||
}
|
||||
proc->updated = true;
|
||||
|
@ -66,7 +66,15 @@ in the source distribution for its full text.
|
||||
#define prop_number_signed_value prop_number_integer_value
|
||||
#endif
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
};
|
||||
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
/*
|
||||
* See /usr/include/sys/signal.h
|
||||
@ -174,8 +182,9 @@ const MeterClass* const Platform_meterTypes[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
/* no platform-specific setup needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_done(void) {
|
||||
|
@ -24,6 +24,7 @@ in the source distribution for its full text.
|
||||
#include "Process.h"
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "generic/hostname.h"
|
||||
#include "generic/uname.h"
|
||||
@ -33,7 +34,9 @@ in the source distribution for its full text.
|
||||
#define PLATFORM_LONG_OPTIONS \
|
||||
// End of list
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
/* see /usr/include/sys/signal.h */
|
||||
extern const SignalItem Platform_signals[];
|
||||
@ -42,7 +45,7 @@ extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
void Platform_done(void);
|
||||
|
||||
@ -82,8 +85,8 @@ static inline void Platform_getRelease(char** string) {
|
||||
|
||||
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
|
||||
|
||||
static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return false;
|
||||
static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
@ -136,7 +136,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
},
|
||||
[ST_UID] = {
|
||||
.name = "ST_UID",
|
||||
.title = " UID ",
|
||||
.title = "UID",
|
||||
.description = "User ID of the process owner",
|
||||
.flags = 0,
|
||||
},
|
||||
@ -146,6 +146,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
.description = "Percentage of the CPU time the process used in the last sampling",
|
||||
.flags = 0,
|
||||
.defaultSortDesc = true,
|
||||
.autoWidth = true,
|
||||
},
|
||||
[PERCENT_NORM_CPU] = {
|
||||
.name = "PERCENT_NORM_CPU",
|
||||
@ -153,6 +154,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
.description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)",
|
||||
.flags = 0,
|
||||
.defaultSortDesc = true,
|
||||
.autoWidth = true,
|
||||
},
|
||||
[PERCENT_MEM] = {
|
||||
.name = "PERCENT_MEM",
|
||||
@ -163,7 +165,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
},
|
||||
[USER] = {
|
||||
.name = "USER",
|
||||
.title = "USER ",
|
||||
.title = "USER ",
|
||||
.description = "Username of the process owner (or user ID if name cannot be determined)",
|
||||
.flags = 0,
|
||||
},
|
||||
@ -203,7 +205,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
};
|
||||
|
||||
Process* OpenBSDProcess_new(const Settings* settings) {
|
||||
OpenBSDProcess* this = xCalloc(sizeof(OpenBSDProcess), 1);
|
||||
OpenBSDProcess* this = xCalloc(1, sizeof(OpenBSDProcess));
|
||||
Object_setClass(this, Class(OpenBSDProcess));
|
||||
Process_init(&this->super, settings);
|
||||
return &this->super;
|
||||
|
@ -309,7 +309,7 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
|
||||
|
||||
OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_CWD) {
|
||||
if (settings->ss->flags & PROCESS_FLAG_CWD) {
|
||||
OpenBSDProcessList_updateCwd(kproc, proc);
|
||||
}
|
||||
|
||||
@ -330,8 +330,11 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
|
||||
fp->addr = kproc->p_addr;
|
||||
proc->m_virt = kproc->p_vm_dsize * pageSizeKB;
|
||||
proc->m_resident = kproc->p_vm_rssize * pageSizeKB;
|
||||
|
||||
proc->percent_mem = proc->m_resident / (float)this->super.totalMem * 100.0F;
|
||||
proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0F, this->super.activeCPUs * 100.0F);
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
proc->nice = kproc->p_nice - 20;
|
||||
proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
|
||||
proc->priority = kproc->p_priority - PZERO;
|
||||
@ -345,15 +348,16 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
|
||||
proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
|
||||
}
|
||||
|
||||
/* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */
|
||||
switch (kproc->p_stat) {
|
||||
case SIDL: proc->state = 'I'; break;
|
||||
case SRUN: proc->state = 'P'; break;
|
||||
case SSLEEP: proc->state = 'S'; break;
|
||||
case SSTOP: proc->state = 'T'; break;
|
||||
case SZOMB: proc->state = 'Z'; break;
|
||||
case SDEAD: proc->state = 'D'; break;
|
||||
case SONPROC: proc->state = 'R'; break;
|
||||
default: proc->state = '?';
|
||||
case SIDL: proc->state = IDLE; break;
|
||||
case SRUN: proc->state = RUNNABLE; break;
|
||||
case SSLEEP: proc->state = SLEEPING; break;
|
||||
case SSTOP: proc->state = STOPPED; break;
|
||||
case SZOMB: proc->state = ZOMBIE; break;
|
||||
case SDEAD: proc->state = DEFUNCT; break;
|
||||
case SONPROC: proc->state = RUNNING; break;
|
||||
default: proc->state = UNKNOWN;
|
||||
}
|
||||
|
||||
if (Process_isKernelThread(proc)) {
|
||||
@ -363,7 +367,7 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
|
||||
}
|
||||
|
||||
this->super.totalTasks++;
|
||||
if (proc->state == 'R') {
|
||||
if (proc->state == RUNNING) {
|
||||
this->super.runningTasks++;
|
||||
}
|
||||
|
||||
@ -388,7 +392,7 @@ static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
|
||||
|
||||
unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
|
||||
|
||||
// XXX Not sure if CP_SPIN should be added to sysAllTime.
|
||||
// XXX Not sure if CP_SPIN should be added to sysAllTime.
|
||||
// See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c
|
||||
#ifdef CP_SPIN
|
||||
sysAllTime += times[CP_SPIN];
|
||||
|
@ -46,7 +46,15 @@ in the source distribution for its full text.
|
||||
#include "openbsd/OpenBSDProcessList.h"
|
||||
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
};
|
||||
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
/*
|
||||
* See /usr/include/sys/signal.h
|
||||
@ -121,8 +129,9 @@ const MeterClass* const Platform_meterTypes[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
/* no platform-specific setup needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_done(void) {
|
||||
|
@ -20,12 +20,15 @@ in the source distribution for its full text.
|
||||
#include "Process.h"
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "generic/hostname.h"
|
||||
#include "generic/uname.h"
|
||||
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
/* see /usr/include/sys/signal.h */
|
||||
extern const SignalItem Platform_signals[];
|
||||
@ -34,7 +37,7 @@ extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
void Platform_done(void);
|
||||
|
||||
@ -76,8 +79,8 @@ static inline void Platform_getRelease(char** string) {
|
||||
|
||||
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
|
||||
|
||||
static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return false;
|
||||
static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
.TH "PCP-HTOP" "5" "2021" "@PACKAGE_STRING@" "File Formats"
|
||||
.TH "PCP-HTOP" "5" "2022" "@PACKAGE_STRING@" "File Formats"
|
||||
.SH "NAME"
|
||||
\f3pcp-htop\f1 \- pcp-htop configuration file
|
||||
.SH "DESCRIPTION"
|
||||
|
@ -166,7 +166,7 @@ bool PCPMetric_fetch(struct timeval* timestamp) {
|
||||
}
|
||||
int sts, count = 0;
|
||||
do {
|
||||
sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result);
|
||||
sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result);
|
||||
} while (sts == PM_ERR_IPC && ++count < 3);
|
||||
if (sts < 0) {
|
||||
if (pmDebugOptions.appl0)
|
||||
|
@ -53,11 +53,11 @@ const ProcessFieldData Process_fields[] = {
|
||||
[M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
|
||||
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, },
|
||||
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, },
|
||||
|
@ -142,10 +142,27 @@ static inline char Metric_instance_char(int metric, int pid, int offset, char fa
|
||||
return fallback;
|
||||
}
|
||||
|
||||
static inline ProcessState PCPProcessList_getProcessState(char state) {
|
||||
switch (state) {
|
||||
case '?': return UNKNOWN;
|
||||
case 'R': return RUNNING;
|
||||
case 'W': return WAITING;
|
||||
case 'D': return UNINTERRUPTIBLE_WAIT;
|
||||
case 'P': return PAGING;
|
||||
case 'T': return STOPPED;
|
||||
case 't': return TRACED;
|
||||
case 'Z': return ZOMBIE;
|
||||
case 'X': return DEFUNCT;
|
||||
case 'I': return IDLE;
|
||||
case 'S': return SLEEPING;
|
||||
default: return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static void PCPProcessList_updateID(Process* process, int pid, int offset) {
|
||||
process->tgid = Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1);
|
||||
process->ppid = Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1);
|
||||
process->state = Metric_instance_char(PCP_PROC_STATE, pid, offset, '?');
|
||||
process->state = PCPProcessList_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?'));
|
||||
}
|
||||
|
||||
static void PCPProcessList_updateInfo(Process* process, int pid, int offset, char* command, size_t commLen) {
|
||||
@ -283,7 +300,7 @@ static void PCPProcessList_updateUsername(Process* process, int pid, int offset,
|
||||
static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) {
|
||||
pmAtomValue value;
|
||||
if (!PCPMetric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) {
|
||||
if (process->state != 'Z')
|
||||
if (process->state != ZOMBIE)
|
||||
process->isKernelThread = true;
|
||||
Process_updateCmdline(process, NULL, 0, 0);
|
||||
return;
|
||||
@ -351,7 +368,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period,
|
||||
if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {
|
||||
proc->updated = true;
|
||||
proc->show = false;
|
||||
if (proc->state == 'R')
|
||||
if (proc->state == RUNNING)
|
||||
pl->runningTasks++;
|
||||
pl->kernelThreads++;
|
||||
pl->totalTasks++;
|
||||
@ -360,19 +377,19 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period,
|
||||
if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {
|
||||
proc->updated = true;
|
||||
proc->show = false;
|
||||
if (proc->state == 'R')
|
||||
if (proc->state == RUNNING)
|
||||
pl->runningTasks++;
|
||||
pl->userlandThreads++;
|
||||
pl->totalTasks++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_IO)
|
||||
if (settings->ss->flags & PROCESS_FLAG_IO)
|
||||
PCPProcessList_updateIO(pp, pid, offset, now);
|
||||
|
||||
PCPProcessList_updateMemory(pp, pid, offset);
|
||||
|
||||
if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) &&
|
||||
if ((settings->ss->flags & PROCESS_FLAG_LINUX_SMAPS) &&
|
||||
(Process_isKernelThread(proc) == false)) {
|
||||
if (PCPMetric_enabled(PCP_PROC_SMAPS_PSS))
|
||||
PCPProcessList_updateSmaps(pp, pid, offset);
|
||||
@ -391,6 +408,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period,
|
||||
proc->percent_cpu = isnan(percent_cpu) ?
|
||||
0.0 : CLAMP(percent_cpu, 0.0, pl->activeCPUs * 100.0);
|
||||
proc->percent_mem = proc->m_resident / (double)pl->totalMem * 100.0;
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
PCPProcessList_updateUsername(proc, pid, offset, pl->usersTable);
|
||||
|
||||
@ -398,29 +416,29 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period,
|
||||
PCPProcessList_updateCmdline(proc, pid, offset, command);
|
||||
Process_fillStarttimeBuffer(proc);
|
||||
ProcessList_add(pl, proc);
|
||||
} else if (settings->updateProcessNames && proc->state != 'Z') {
|
||||
} else if (settings->updateProcessNames && proc->state != ZOMBIE) {
|
||||
PCPProcessList_updateCmdline(proc, pid, offset, command);
|
||||
}
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_CGROUP)
|
||||
if (settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP)
|
||||
PCPProcessList_readCGroups(pp, pid, offset);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_OOM)
|
||||
if (settings->ss->flags & PROCESS_FLAG_LINUX_OOM)
|
||||
PCPProcessList_readOomData(pp, pid, offset);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_CTXT)
|
||||
if (settings->ss->flags & PROCESS_FLAG_LINUX_CTXT)
|
||||
PCPProcessList_readCtxtData(pp, pid, offset);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_SECATTR)
|
||||
if (settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR)
|
||||
PCPProcessList_readSecattrData(pp, pid, offset);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_CWD)
|
||||
if (settings->ss->flags & PROCESS_FLAG_CWD)
|
||||
PCPProcessList_readCwd(pp, pid, offset);
|
||||
|
||||
if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP)
|
||||
if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP)
|
||||
PCPProcessList_readAutogroup(pp, pid, offset);
|
||||
|
||||
if (proc->state == 'Z' && !proc->cmdline && command[0]) {
|
||||
if (proc->state == ZOMBIE && !proc->cmdline && command[0]) {
|
||||
Process_updateCmdline(proc, command, 0, strlen(command));
|
||||
} else if (Process_isThread(proc)) {
|
||||
if ((settings->showThreadNames || Process_isKernelThread(proc)) && command[0]) {
|
||||
@ -439,7 +457,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period,
|
||||
(hideUserlandThreads && Process_isUserlandThread(proc)));
|
||||
|
||||
pl->totalTasks++;
|
||||
if (proc->state == 'R')
|
||||
if (proc->state == RUNNING)
|
||||
pl->runningTasks++;
|
||||
proc->updated = true;
|
||||
}
|
||||
@ -659,16 +677,16 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
||||
for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++)
|
||||
PCPMetric_enable(metric, enabled);
|
||||
|
||||
flagged = settings->flags & PROCESS_FLAG_LINUX_CGROUP;
|
||||
flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CGROUP;
|
||||
PCPMetric_enable(PCP_PROC_CGROUPS, flagged && enabled);
|
||||
flagged = settings->flags & PROCESS_FLAG_LINUX_OOM;
|
||||
flagged = settings->ss->flags & PROCESS_FLAG_LINUX_OOM;
|
||||
PCPMetric_enable(PCP_PROC_OOMSCORE, flagged && enabled);
|
||||
flagged = settings->flags & PROCESS_FLAG_LINUX_CTXT;
|
||||
flagged = settings->ss->flags & PROCESS_FLAG_LINUX_CTXT;
|
||||
PCPMetric_enable(PCP_PROC_VCTXSW, flagged && enabled);
|
||||
PCPMetric_enable(PCP_PROC_NVCTXSW, flagged && enabled);
|
||||
flagged = settings->flags & PROCESS_FLAG_LINUX_SECATTR;
|
||||
flagged = settings->ss->flags & PROCESS_FLAG_LINUX_SECATTR;
|
||||
PCPMetric_enable(PCP_PROC_LABELS, flagged && enabled);
|
||||
flagged = settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP;
|
||||
flagged = settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP;
|
||||
PCPMetric_enable(PCP_PROC_AUTOGROUP_ID, flagged && enabled);
|
||||
PCPMetric_enable(PCP_PROC_AUTOGROUP_NICE, flagged && enabled);
|
||||
|
||||
|
@ -54,9 +54,20 @@ in the source distribution for its full text.
|
||||
|
||||
Platform* pcp;
|
||||
|
||||
ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, (int)M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
{
|
||||
.name = "I/O",
|
||||
.columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command",
|
||||
.sortKey = "IO_RATE",
|
||||
},
|
||||
};
|
||||
|
||||
int Platform_numberOfFields = LAST_PROCESSFIELD;
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
const SignalItem Platform_signals[] = {
|
||||
{ .name = " 0 Cancel", .number = 0 },
|
||||
@ -257,7 +268,7 @@ size_t Platform_addMetric(PCPMetric id, const char* name) {
|
||||
/* global state from the environment and command line arguments */
|
||||
pmOptions opts;
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
const char* source;
|
||||
if (opts.context == PM_CONTEXT_ARCHIVE) {
|
||||
source = opts.archives[0];
|
||||
@ -277,12 +288,12 @@ void Platform_init(void) {
|
||||
}
|
||||
if (sts < 0) {
|
||||
fprintf(stderr, "Cannot setup PCP metric source: %s\n", pmErrStr(sts));
|
||||
exit(1);
|
||||
return false;
|
||||
}
|
||||
/* setup timezones and other general startup preparation completion */
|
||||
if (pmGetContextOptions(sts, &opts) < 0 || opts.errors) {
|
||||
pmflush();
|
||||
exit(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
pcp = xCalloc(1, sizeof(Platform));
|
||||
@ -309,7 +320,8 @@ void Platform_init(void) {
|
||||
sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids);
|
||||
if (sts < 0) {
|
||||
fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts));
|
||||
exit(1);
|
||||
Platform_done();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < pcp->totalMetrics; i++) {
|
||||
@ -361,6 +373,8 @@ void Platform_init(void) {
|
||||
Platform_getRelease(0);
|
||||
Platform_getMaxCPU();
|
||||
Platform_getMaxPid();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_dynamicColumnsDone(Hashtable* columns) {
|
||||
@ -697,16 +711,16 @@ void Platform_longOptionsUsage(ATTR_UNUSED const char* name) {
|
||||
" --timezone=TZ set reporting timezone\n");
|
||||
}
|
||||
|
||||
bool Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) {
|
||||
CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) {
|
||||
/* libpcp export without a header definition */
|
||||
extern void __pmAddOptHost(pmOptions*, char*);
|
||||
|
||||
switch (opt) {
|
||||
case PLATFORM_LONGOPT_HOST: /* --host=HOSTSPEC */
|
||||
if (argv[optind][0] == '\0')
|
||||
return false;
|
||||
return STATUS_ERROR_EXIT;
|
||||
__pmAddOptHost(&opts, optarg);
|
||||
return true;
|
||||
return STATUS_OK;
|
||||
|
||||
case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */
|
||||
if (opts.timezone) {
|
||||
@ -715,23 +729,23 @@ bool Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) {
|
||||
} else {
|
||||
opts.tzflag = 1;
|
||||
}
|
||||
return true;
|
||||
return STATUS_OK;
|
||||
|
||||
case PLATFORM_LONGOPT_TIMEZONE: /* --timezone=TZ */
|
||||
if (argv[optind][0] == '\0')
|
||||
return false;
|
||||
return STATUS_ERROR_EXIT;
|
||||
if (opts.tzflag) {
|
||||
pmprintf("%s: at most one of -Z and -z allowed\n", pmGetProgname());
|
||||
opts.errors++;
|
||||
} else {
|
||||
opts.timezone = optarg;
|
||||
}
|
||||
return true;
|
||||
return STATUS_OK;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
@ -34,6 +34,7 @@ in the source distribution for its full text.
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "RichString.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
|
||||
#include "pcp/PCPDynamicColumn.h"
|
||||
#include "pcp/PCPDynamicMeter.h"
|
||||
@ -57,9 +58,9 @@ typedef struct Platform_ {
|
||||
unsigned int ncpu; /* maximum processor count configured */
|
||||
} Platform;
|
||||
|
||||
extern ProcessField Platform_defaultFields[];
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern int Platform_numberOfFields;
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
extern const SignalItem Platform_signals[];
|
||||
|
||||
@ -67,7 +68,7 @@ extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
void Platform_done(void);
|
||||
|
||||
@ -126,7 +127,7 @@ enum {
|
||||
|
||||
void Platform_longOptionsUsage(const char* name);
|
||||
|
||||
bool Platform_getLongOption(int opt, int argc, char** argv);
|
||||
CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);
|
||||
|
||||
extern pmOptions opts;
|
||||
|
||||
|
@ -40,6 +40,16 @@ in the source distribution for its full text.
|
||||
#include "SolarisProcessList.h"
|
||||
|
||||
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Default",
|
||||
.columns = "PID LWPID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
};
|
||||
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
const SignalItem Platform_signals[] = {
|
||||
{ .name = " 0 Cancel", .number = 0 },
|
||||
{ .name = " 1 SIGHUP", .number = 1 },
|
||||
@ -87,8 +97,6 @@ const SignalItem Platform_signals[] = {
|
||||
|
||||
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, LWPID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
|
||||
const MeterClass* const Platform_meterTypes[] = {
|
||||
&CPUMeter_class,
|
||||
&ClockMeter_class,
|
||||
@ -122,8 +130,9 @@ const MeterClass* const Platform_meterTypes[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
/* no platform-specific setup needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_done(void) {
|
||||
|
@ -35,6 +35,7 @@ in the source distribution for its full text.
|
||||
#include "NetworkIOMeter.h"
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "generic/hostname.h"
|
||||
#include "generic/uname.h"
|
||||
@ -51,15 +52,17 @@ typedef struct envAccum_ {
|
||||
char* env;
|
||||
} envAccum;
|
||||
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
extern const SignalItem Platform_signals[];
|
||||
|
||||
extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
void Platform_done(void);
|
||||
|
||||
@ -105,8 +108,8 @@ static inline void Platform_getRelease(char** string) {
|
||||
|
||||
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
|
||||
|
||||
static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return false;
|
||||
static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
@ -39,11 +39,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
|
||||
[PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, },
|
||||
[M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, },
|
||||
[M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, },
|
||||
[ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, },
|
||||
[PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },
|
||||
[PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, },
|
||||
[TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, },
|
||||
[NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, },
|
||||
[TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, },
|
||||
|
@ -363,6 +363,19 @@ static void SolarisProcessList_updateCwd(pid_t pid, Process* proc) {
|
||||
free_and_xStrdup(&proc->procCwd, target);
|
||||
}
|
||||
|
||||
/* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */
|
||||
static inline ProcessState SolarisProcessList_getProcessState(char state) {
|
||||
switch (state) {
|
||||
case 'S': return SLEEPING;
|
||||
case 'R': return RUNNABLE;
|
||||
case 'O': return RUNNING;
|
||||
case 'Z': return ZOMBIE;
|
||||
case 'T': return STOPPED;
|
||||
case 'I': return IDLE;
|
||||
default: return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: the following is a callback function of type proc_walk_f
|
||||
* and MUST conform to the appropriate definition in order
|
||||
* to work. See libproc(3LIB) on a Solaris or Illumos
|
||||
@ -402,7 +415,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo,
|
||||
proc->priority = _lwpsinfo->pr_pri;
|
||||
proc->nice = _lwpsinfo->pr_nice - NZERO;
|
||||
proc->processor = _lwpsinfo->pr_onpro;
|
||||
proc->state = _lwpsinfo->pr_sname;
|
||||
proc->state = SolarisProcessList_getProcessState(_lwpsinfo->pr_sname);
|
||||
// NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000
|
||||
// Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html
|
||||
// (accessed on 18 November 2017)
|
||||
@ -438,7 +451,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo,
|
||||
Process_updateComm(proc, _psinfo->pr_fname);
|
||||
Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0);
|
||||
|
||||
if (proc->settings->flags & PROCESS_FLAG_CWD) {
|
||||
if (proc->settings->ss->flags & PROCESS_FLAG_CWD) {
|
||||
SolarisProcessList_updateCwd(_psinfo->pr_pid, proc);
|
||||
}
|
||||
}
|
||||
@ -450,8 +463,11 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo,
|
||||
proc->tgid = (_psinfo->pr_ppid * 1024);
|
||||
sproc->realppid = _psinfo->pr_ppid;
|
||||
sproc->realtgid = _psinfo->pr_ppid;
|
||||
|
||||
// See note above (in common section) about this BINARY FRACTION
|
||||
proc->percent_cpu = ((uint16_t)_psinfo->pr_pctcpu / (double)32768) * (double)100.0;
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
proc->time = _psinfo->pr_time.tv_sec;
|
||||
if (!preExisting) { // Tasks done only for NEW processes
|
||||
proc->isUserlandThread = false;
|
||||
@ -462,11 +478,11 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo,
|
||||
if (proc->isKernelThread && !pl->settings->hideKernelThreads) {
|
||||
pl->kernelThreads += proc->nlwp;
|
||||
pl->totalTasks += proc->nlwp + 1;
|
||||
if (proc->state == 'O') {
|
||||
if (proc->state == RUNNING) {
|
||||
pl->runningTasks++;
|
||||
}
|
||||
} else if (!proc->isKernelThread) {
|
||||
if (proc->state == 'O') {
|
||||
if (proc->state == RUNNING) {
|
||||
pl->runningTasks++;
|
||||
}
|
||||
if (pl->settings->hideUserlandThreads) {
|
||||
@ -479,6 +495,8 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo,
|
||||
proc->show = !(pl->settings->hideKernelThreads && proc->isKernelThread);
|
||||
} else { // We are not in the master LWP, so jump to the LWP handling code
|
||||
proc->percent_cpu = ((uint16_t)_lwpsinfo->pr_pctcpu / (double)32768) * (double)100.0;
|
||||
Process_updateCPUFieldWidths(proc->percent_cpu);
|
||||
|
||||
proc->time = _lwpsinfo->pr_time.tv_sec;
|
||||
if (!preExisting) { // Tasks done only for NEW LWPs
|
||||
proc->isUserlandThread = true;
|
||||
|
@ -27,14 +27,22 @@ in the source distribution for its full text.
|
||||
#include "UptimeMeter.h"
|
||||
|
||||
|
||||
const ScreenDefaults Platform_defaultScreens[] = {
|
||||
{
|
||||
.name = "Main",
|
||||
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
|
||||
.sortKey = "PERCENT_CPU",
|
||||
},
|
||||
};
|
||||
|
||||
const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
|
||||
|
||||
const SignalItem Platform_signals[] = {
|
||||
{ .name = " 0 Cancel", .number = 0 },
|
||||
};
|
||||
|
||||
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
|
||||
|
||||
const ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 };
|
||||
|
||||
const MeterClass* const Platform_meterTypes[] = {
|
||||
&CPUMeter_class,
|
||||
&ClockMeter_class,
|
||||
@ -68,8 +76,9 @@ const MeterClass* const Platform_meterTypes[] = {
|
||||
|
||||
static const char Platform_unsupported[] = "unsupported";
|
||||
|
||||
void Platform_init(void) {
|
||||
bool Platform_init(void) {
|
||||
/* no platform-specific setup needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform_done(void) {
|
||||
|
@ -15,19 +15,22 @@ in the source distribution for its full text.
|
||||
#include "NetworkIOMeter.h"
|
||||
#include "ProcessLocksScreen.h"
|
||||
#include "SignalsPanel.h"
|
||||
#include "CommandLine.h"
|
||||
#include "generic/gettime.h"
|
||||
#include "unsupported/UnsupportedProcess.h"
|
||||
|
||||
|
||||
extern const ScreenDefaults Platform_defaultScreens[];
|
||||
|
||||
extern const unsigned int Platform_numberOfDefaultScreens;
|
||||
|
||||
extern const SignalItem Platform_signals[];
|
||||
|
||||
extern const unsigned int Platform_numberOfSignals;
|
||||
|
||||
extern const ProcessField Platform_defaultFields[];
|
||||
|
||||
extern const MeterClass* const Platform_meterTypes[];
|
||||
|
||||
void Platform_init(void);
|
||||
bool Platform_init(void);
|
||||
|
||||
void Platform_done(void);
|
||||
|
||||
@ -65,8 +68,8 @@ void Platform_getRelease(char** string);
|
||||
|
||||
static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }
|
||||
|
||||
static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return false;
|
||||
static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {
|
||||
return STATUS_ERROR_EXIT;
|
||||
}
|
||||
|
||||
static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user