Export individual processes stats #794

This commit is contained in:
nicolargo 2024-04-06 17:47:09 +02:00
commit 5f48d5c71b
15 changed files with 414 additions and 272 deletions

View File

@ -357,6 +357,10 @@ nice_warning=-20,-19,-18,-17,-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2
#nice_careful=1,2,3,4,5,6,7,8,9
#nice_warning=10,11,12,13,14
#nice_critical=15,16,17,18,19
#
# Define the list of processes to export using:
# a comma-separated list of Glances filter
#export=.*firefox.*,pid:1234
[ports]
disable=False

View File

@ -357,6 +357,12 @@ nice_warning=-20,-19,-18,-17,-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2
#nice_careful=1,2,3,4,5,6,7,8,9
#nice_warning=10,11,12,13,14
#nice_critical=15,16,17,18,19
#
# Define the list of processes to export using:
# a comma-separated list of regular expression (apply on name and cmdline)
#export=.*firefox.*
# or an uniq key:value filter
#export=pid:1234
[ports]
disable=False

View File

@ -199,4 +199,67 @@ Accumulated per program — key 'j'
When activated ('j' hotkey or --programs option in the command line), processes are merged
to display which programs are active. The columns show the accumulated cpu consumption, the
accumulated virtual and resident memory consumption, the accumulated transferred data I/O.
The PID columns is replaced by a NPROCS column which is the number of processes.
The PID columns is replaced by a NPROCS column which is the number of processes.
Export process
--------------
Glances version 4 introduces a new feature to export specifics processes. In order to use this
feature, you need to use the export option in the processlist section of the Glances configuration
file or the --export-process-filter option in the command line.
The export option is a list of Glances filters.
Example number one, export all processes named 'python' (or with a command line containing 'python'):
.. code-block:: ini
[processlist]
export=.*python.*
Note: or the --export-process-filter ".*python.*" option in the command line.
Example number two, export all processes with the name 'python' or 'bash':
.. code-block:: ini
[processlist]
export=.*python.*,.*bash.*
Note: or the --export-process-filter ".*python.*,.*bash.*" option in the command line.
Example number three, export all processes belong to 'nicolargo' user:
.. code-block:: ini
[processlist]
export=username:nicolargo
Note: or the --export-process-filter "username:nicolargo" option in the command line.
The output of the export use the PID as the key (for example if you want to export firefox process
to a CSV file):
Configuration file (glances.conf):
.. code-block:: ini
[processlist]
export=.*firefox.*
Note: or the --export-process-filter ".*firefox.*" option in the command line.
Command line example:
.. code-block:: bash
glances -C ./conf/glances.conf --export csv --export-csv-file /tmp/glances.csv --disable-plugin all --enable-plugin processlist --quiet
the result will be:
.. code-block:: csv
timestamp,845992.memory_percent,845992.status,845992.num_threads,845992.cpu_timesuser,845992.cpu_timessystem,845992.cpu_timeschildren_user,845992.cpu_timeschildren_system,845992.cpu_timesiowait,845992.memory_inforss,845992.memory_infovms,845992.memory_infoshared,845992.memory_infotext,845992.memory_infolib,845992.memory_infodata,845992.memory_infodirty,845992.name,845992.io_counters,845992.nice,845992.cpu_percent,845992.pid,845992.gidsreal,845992.gidseffective,845992.gidssaved,845992.key,845992.time_since_update,845992.cmdline,845992.username,total,running,sleeping,thread,pid_max
2024-04-03 18:39:55,3.692938041968513,S,138,1702.88,567.89,1752.79,244.18,0.0,288919552,12871561216,95182848,856064,0,984535040,0,firefox,1863281664,0,0.5,845992,1000,1000,1000,pid,2.2084147930145264,/snap/firefox/3836/usr/lib/firefox/firefox,nicolargo,403,1,333,1511,0
2024-04-03 18:39:57,3.692938041968513,S,138,1702.88,567.89,1752.79,244.18,0.0,288919552,12871561216,95182848,856064,0,984535040,0,firefox,1863281664,0,0.5,845992,1000,1000,1000,pid,2.2084147930145264,/snap/firefox/3836/usr/lib/firefox/firefox,nicolargo,403,1,333,1511,0

View File

@ -141,7 +141,7 @@ Get plugin stats::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.42368555068969727},
"timer": 0.3346278667449951},
{"count": 0,
"countmax": 20.0,
"countmin": None,
@ -150,7 +150,7 @@ Get plugin stats::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.42352890968322754}]
"timer": 0.3344759941101074}]
Fields descriptions:
@ -178,7 +178,7 @@ Get a specific item when field matches the given value::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.42368555068969727}]}
"timer": 0.3346278667449951}]}
GET cloud
---------
@ -226,14 +226,18 @@ Get plugin stats::
"engine": "docker",
"id": "3abd51c615968482d9ccff5afc629f267f6dda113ed68b75b432615fae3b49fb",
"image": ["portainer/portainer-ce:2.9.3"],
"io": {},
"io": {"cumulative_ior": 110592, "cumulative_iow": 962560},
"key": "name",
"memory": {},
"memory_usage": None,
"memory": {"cache": None,
"limit": 7823568896,
"max_usage": None,
"rss": None,
"usage": 15851520},
"memory_usage": 15851520,
"name": "portainer",
"network": {},
"network": {"cumulative_rx": 1803247, "cumulative_tx": 1636},
"status": "running",
"uptime": "1 weeks"}]
"uptime": "6 days"}]
Fields descriptions:
@ -269,14 +273,18 @@ Get a specific item when field matches the given value::
"engine": "docker",
"id": "3abd51c615968482d9ccff5afc629f267f6dda113ed68b75b432615fae3b49fb",
"image": ["portainer/portainer-ce:2.9.3"],
"io": {},
"io": {"cumulative_ior": 110592, "cumulative_iow": 962560},
"key": "name",
"memory": {},
"memory_usage": None,
"memory": {"cache": None,
"limit": 7823568896,
"max_usage": None,
"rss": None,
"usage": 15851520},
"memory_usage": 15851520,
"name": "portainer",
"network": {},
"network": {"cumulative_rx": 1803247, "cumulative_tx": 1636},
"status": "running",
"uptime": "1 weeks"}]}
"uptime": "6 days"}]}
GET core
--------
@ -303,19 +311,19 @@ Get plugin stats::
# curl http://localhost:61208/api/4/cpu
{"cpucore": 4,
"ctx_switches": 483265492,
"ctx_switches": 1083773007,
"guest": 0.0,
"idle": 72.4,
"interrupts": 271401643,
"iowait": 0.3,
"idle": 71.2,
"interrupts": 497643356,
"iowait": 0.0,
"irq": 0.0,
"nice": 0.0,
"soft_interrupts": 128147575,
"soft_interrupts": 222951171,
"steal": 0.0,
"syscalls": 0,
"system": 4.7,
"total": 27.3,
"user": 22.6}
"system": 3.1,
"total": 28.8,
"user": 25.8}
Fields descriptions:
@ -348,7 +356,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/cpu/total
{"total": 27.3}
{"total": 28.8}
GET diskio
----------
@ -358,16 +366,16 @@ Get plugin stats::
# curl http://localhost:61208/api/4/diskio
[{"disk_name": "sda",
"key": "disk_name",
"read_bytes": 66748604928,
"read_count": 2974717,
"write_bytes": 168312320000,
"write_count": 1561378},
"read_bytes": 106861217280,
"read_count": 5848339,
"write_bytes": 209200316416,
"write_count": 2631934},
{"disk_name": "sda1",
"key": "disk_name",
"read_bytes": 15003648,
"read_count": 432,
"read_bytes": 24269824,
"read_count": 659,
"write_bytes": 0,
"write_count": 34}]
"write_count": 44}]
Fields descriptions:
@ -396,10 +404,10 @@ Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/diskio/disk_name/sda
{"sda": [{"disk_name": "sda",
"key": "disk_name",
"read_bytes": 66748604928,
"read_count": 2974717,
"write_bytes": 168312320000,
"write_count": 1561378}]}
"read_bytes": 106861217280,
"read_count": 5848339,
"write_bytes": 209200316416,
"write_count": 2631934}]}
GET folders
-----------
@ -426,13 +434,13 @@ Get plugin stats::
# curl http://localhost:61208/api/4/fs
[{"device_name": "/dev/mapper/ubuntu--gnome--vg-root",
"free": 36907282432,
"free": 35840458752,
"fs_type": "ext4",
"key": "mnt_point",
"mnt_point": "/",
"percent": 84.0,
"percent": 84.5,
"size": 243334156288,
"used": 194039418880},
"used": 195106242560},
{"device_name": "zsfpool",
"free": 31195136,
"fs_type": "zfs",
@ -461,13 +469,13 @@ Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/fs/mnt_point//
{"/": [{"device_name": "/dev/mapper/ubuntu--gnome--vg-root",
"free": 36907282432,
"free": 35840458752,
"fs_type": "ext4",
"key": "mnt_point",
"mnt_point": "/",
"percent": 84.0,
"percent": 84.5,
"size": 243334156288,
"used": 194039418880}]}
"used": 195106242560}]}
GET gpu
-------
@ -500,11 +508,11 @@ GET ip
Get plugin stats::
# curl http://localhost:61208/api/4/ip
{"address": "192.168.0.32",
"gateway": "192.168.0.254",
{"address": "192.168.1.14",
"gateway": "192.168.1.1",
"mask": "255.255.255.0",
"mask_cidr": 24,
"public_address": "91.166.228.228",
"public_address": "92.151.148.66",
"public_info_human": ""}
Fields descriptions:
@ -519,7 +527,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/ip/gateway
{"gateway": "192.168.0.254"}
{"gateway": "192.168.1.1"}
GET irq
-------
@ -540,10 +548,7 @@ GET load
Get plugin stats::
# curl http://localhost:61208/api/4/load
{"cpucore": 4,
"min1": 0.91845703125,
"min15": 0.896484375,
"min5": 0.79931640625}
{"cpucore": 4, "min1": 0.1416015625, "min15": 0.970703125, "min5": 0.765625}
Fields descriptions:
@ -555,7 +560,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/load/min1
{"min1": 0.91845703125}
{"min1": 0.1416015625}
GET mem
-------
@ -563,16 +568,16 @@ GET mem
Get plugin stats::
# curl http://localhost:61208/api/4/mem
{"active": 2973868032,
"available": 2273656832,
"buffers": 282787840,
"cached": 2105749504,
"free": 2273656832,
"inactive": 2872504320,
"percent": 70.9,
"shared": 598622208,
{"active": 2535997440,
"available": 2432135168,
"buffers": 501940224,
"cached": 2622812160,
"free": 2432135168,
"inactive": 3695976448,
"percent": 68.9,
"shared": 652316672,
"total": 7823568896,
"used": 5549912064}
"used": 5391433728}
Fields descriptions:
@ -599,13 +604,13 @@ GET memswap
Get plugin stats::
# curl http://localhost:61208/api/4/memswap
{"free": 3853631488,
"percent": 52.3,
"sin": 4358995968,
"sout": 9174384640,
{"free": 6828023808,
"percent": 15.5,
"sin": 8335540224,
"sout": 16940646400,
"time_since_update": 1,
"total": 8082419712,
"used": 4228788224}
"used": 1254395904}
Fields descriptions:
@ -630,15 +635,15 @@ Get plugin stats::
# curl http://localhost:61208/api/4/network
[{"alias": None,
"bytes_all": 0,
"bytes_all_gauge": 5346184024,
"bytes_all_gauge": 9770615278,
"bytes_recv": 0,
"bytes_recv_gauge": 5021772215,
"bytes_recv_gauge": 9183565245,
"bytes_sent": 0,
"bytes_sent_gauge": 324411809,
"bytes_sent_gauge": 587050033,
"interface_name": "wlp2s0",
"key": "interface_name",
"speed": 0,
"time_since_update": 0.3248739242553711},
"time_since_update": 0.22126293182373047},
{"alias": None,
"bytes_all": 0,
"bytes_all_gauge": 0,
@ -649,7 +654,7 @@ Get plugin stats::
"interface_name": "br-40875d2e2716",
"key": "interface_name",
"speed": 0,
"time_since_update": 0.3248739242553711}]
"time_since_update": 0.22126293182373047}]
Fields descriptions:
@ -677,22 +682,23 @@ Get a specific field::
"lxdbr0",
"veth05608da0",
"mpqemubr0",
"veth3c5f47a"]}
"veth601e59cb",
"vethfd301c0"]}
Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/network/interface_name/wlp2s0
{"wlp2s0": [{"alias": None,
"bytes_all": 0,
"bytes_all_gauge": 5346184024,
"bytes_all_gauge": 9770615278,
"bytes_recv": 0,
"bytes_recv_gauge": 5021772215,
"bytes_recv_gauge": 9183565245,
"bytes_sent": 0,
"bytes_sent_gauge": 324411809,
"bytes_sent_gauge": 587050033,
"interface_name": "wlp2s0",
"key": "interface_name",
"speed": 0,
"time_since_update": 0.3248739242553711}]}
"time_since_update": 0.22126293182373047}]}
GET now
-------
@ -700,7 +706,7 @@ GET now
Get plugin stats::
# curl http://localhost:61208/api/4/now
"2024-03-23 09:37:55 CET"
"2024-04-06 17:45:25 CEST"
GET percpu
----------
@ -711,20 +717,7 @@ Get plugin stats::
[{"cpu_number": 0,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 45.0,
"iowait": 1.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 6.0,
"total": 55.0,
"user": 25.0},
{"cpu_number": 1,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 48.0,
"idle": 10.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -732,8 +725,21 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 4.0,
"total": 52.0,
"user": 24.0}]
"total": 90.0,
"user": 52.0},
{"cpu_number": 1,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 53.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 2.0,
"total": 47.0,
"user": 11.0}]
Fields descriptions:
@ -762,12 +768,12 @@ Get plugin stats::
# curl http://localhost:61208/api/4/ports
[{"description": "DefaultGateway",
"host": "192.168.0.254",
"host": "192.168.1.1",
"indice": "port_0",
"port": 0,
"refresh": 30,
"rtt_warning": None,
"status": 0.00386,
"status": 0.007423,
"timeout": 3}]
Fields descriptions:
@ -784,19 +790,19 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/ports/host
{"host": ["192.168.0.254"]}
{"host": ["192.168.1.1"]}
Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/ports/host/192.168.0.254
{"192.168.0.254": [{"description": "DefaultGateway",
"host": "192.168.0.254",
"indice": "port_0",
"port": 0,
"refresh": 30,
"rtt_warning": None,
"status": 0.00386,
"timeout": 3}]}
# curl http://localhost:61208/api/4/ports/host/192.168.1.1
{"192.168.1.1": [{"description": "DefaultGateway",
"host": "192.168.1.1",
"indice": "port_0",
"port": 0,
"refresh": 30,
"rtt_warning": None,
"status": 0.007423,
"timeout": 3}]}
GET processcount
----------------
@ -804,7 +810,7 @@ GET processcount
Get plugin stats::
# curl http://localhost:61208/api/4/processcount
{"pid_max": 0, "running": 1, "sleeping": 334, "thread": 1655, "total": 403}
{"pid_max": 0, "running": 1, "sleeping": 333, "thread": 1520, "total": 403}
Fields descriptions:
@ -857,30 +863,17 @@ GET quicklook
Get plugin stats::
# curl http://localhost:61208/api/4/quicklook
{"cpu": 27.3,
{"cpu": 28.8,
"cpu_hz": 2025000000.0,
"cpu_hz_current": 2034554500.0,
"cpu_hz_current": 1723628500.0,
"cpu_name": "Intel(R) Core(TM) i7-4500U CPU @ 1.80GHz",
"cpucore": 4,
"load": 22.4,
"mem": 70.9,
"load": 24.3,
"mem": 68.9,
"percpu": [{"cpu_number": 0,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 45.0,
"iowait": 1.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 6.0,
"total": 55.0,
"user": 25.0},
{"cpu_number": 1,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 48.0,
"idle": 10.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -888,35 +881,48 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 4.0,
"total": 52.0,
"user": 24.0},
"total": 90.0,
"user": 52.0},
{"cpu_number": 1,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 53.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 2.0,
"total": 47.0,
"user": 11.0},
{"cpu_number": 2,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 64.0,
"idle": 62.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 2.0,
"total": 36.0,
"user": 9.0},
"system": 1.0,
"total": 38.0,
"user": 2.0},
{"cpu_number": 3,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 66.0,
"idle": 62.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 2.0,
"total": 34.0,
"user": 10.0}],
"swap": 52.3}
"system": 1.0,
"total": 38.0,
"user": 2.0}],
"swap": 15.5}
Fields descriptions:
@ -1036,7 +1042,7 @@ GET uptime
Get plugin stats::
# curl http://localhost:61208/api/4/uptime
"19 days, 0:37:13"
"33 days, 8:44:36"
GET version
-----------
@ -1068,85 +1074,7 @@ GET top n items of a specific plugin
Get top 2 processes of the processlist plugin::
# curl http://localhost:61208/api/4/processlist/top/2
[{"cmdline": ["/usr/share/code/code",
"--type=renderer",
"--crashpad-handler-pid=35523",
"--enable-crash-reporter=721e05a9-6035-4dcb-bd58-68097aa48dd0,no_channel",
"--user-data-dir=/home/nicolargo/.config/Code",
"--standard-schemes=vscode-webview,vscode-file",
"--enable-sandbox",
"--secure-schemes=vscode-webview,vscode-file",
"--cors-schemes=vscode-webview,vscode-file",
"--fetch-schemes=vscode-webview,vscode-file",
"--service-worker-schemes=vscode-webview",
"--code-cache-schemes=vscode-webview,vscode-file",
"--app-path=/usr/share/code/resources/app",
"--enable-sandbox",
"--enable-blink-features=HighlightAPI",
"--first-renderer-process",
"--lang=en-US",
"--num-raster-threads=2",
"--enable-main-frame-before-activation",
"--renderer-client-id=4",
"--time-ticks-at-unix-epoch=-1709539275787032",
"--launch-time-ticks=3773104105",
"--shared-files=v8_context_snapshot_data:100",
"--field-trial-handle=0,i,2992833077465328896,17440056338018087593,262144",
"--disable-features=CalculateNativeWinOcclusion,SpareRendererForSitePerProcess",
"--vscode-window-config=vscode:0a3f42ef-a12c-453d-8061-6c7b57ac6b4f"],
"cpu_percent": 0.0,
"cpu_times": {"children_system": 0.0,
"children_user": 0.0,
"iowait": 0.0,
"system": 710.62,
"user": 8468.8},
"gids": {"effective": 1000, "real": 1000, "saved": 1000},
"io_counters": [544399360, 3325952, 0, 0, 0],
"key": "pid",
"memory_info": {"data": 1233420288,
"dirty": 0,
"lib": 0,
"rss": 499662848,
"shared": 67067904,
"text": 126423040,
"vms": 1221792747520},
"memory_percent": 6.386635749516636,
"name": "code",
"nice": 0,
"num_threads": 14,
"pid": 35570,
"status": "S",
"time_since_update": 1,
"username": "nicolargo"},
{"cmdline": ["/usr/share/code/code",
"/home/nicolargo/.vscode/extensions/ms-python.vscode-pylance-2024.2.2/dist/server.bundle.js",
"--cancellationReceive=file:a926d4bb77e62306671377ffa0d7cb38591c07e817",
"--node-ipc",
"--clientProcessId=35618"],
"cpu_percent": 0.0,
"cpu_times": {"children_system": 0.34,
"children_user": 4.37,
"iowait": 0.0,
"system": 202.23,
"user": 4568.12},
"gids": {"effective": 1000, "real": 1000, "saved": 1000},
"io_counters": [619593728, 1871872, 0, 0, 0],
"key": "pid",
"memory_info": {"data": 888487936,
"dirty": 0,
"lib": 0,
"rss": 465555456,
"shared": 20754432,
"text": 126423040,
"vms": 1208849584128},
"memory_percent": 5.9506788038644,
"name": "code",
"nice": 0,
"num_threads": 13,
"pid": 36130,
"status": "S",
"time_since_update": 1,
"username": "nicolargo"}]
[]
Note: Only work for plugin with a list of items
@ -1174,34 +1102,34 @@ GET stats history
History of a plugin::
# curl http://localhost:61208/api/4/cpu/history
{"system": [["2024-03-23T09:37:57.408003", 4.7],
["2024-03-23T09:37:58.434789", 3.6],
["2024-03-23T09:37:59.644529", 3.6]],
"user": [["2024-03-23T09:37:57.407990", 22.6],
["2024-03-23T09:37:58.434780", 7.2],
["2024-03-23T09:37:59.644514", 7.2]]}
{"system": [["2024-04-06T17:45:26.720681", 3.1],
["2024-04-06T17:45:27.742109", 2.2],
["2024-04-06T17:45:28.929983", 2.2]],
"user": [["2024-04-06T17:45:26.720667", 25.8],
["2024-04-06T17:45:27.742099", 7.5],
["2024-04-06T17:45:28.929968", 7.5]]}
Limit history to last 2 values::
# curl http://localhost:61208/api/4/cpu/history/2
{"system": [["2024-03-23T09:37:58.434789", 3.6],
["2024-03-23T09:37:59.644529", 3.6]],
"user": [["2024-03-23T09:37:58.434780", 7.2],
["2024-03-23T09:37:59.644514", 7.2]]}
{"system": [["2024-04-06T17:45:27.742109", 2.2],
["2024-04-06T17:45:28.929983", 2.2]],
"user": [["2024-04-06T17:45:27.742099", 7.5],
["2024-04-06T17:45:28.929968", 7.5]]}
History for a specific field::
# curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-03-23T09:37:55.811463", 4.7],
["2024-03-23T09:37:57.408003", 4.7],
["2024-03-23T09:37:58.434789", 3.6],
["2024-03-23T09:37:59.644529", 3.6]]}
{"system": [["2024-04-06T17:45:25.061145", 3.1],
["2024-04-06T17:45:26.720681", 3.1],
["2024-04-06T17:45:27.742109", 2.2],
["2024-04-06T17:45:28.929983", 2.2]]}
Limit history for a specific field to last 2 values::
# curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-03-23T09:37:58.434789", 3.6],
["2024-03-23T09:37:59.644529", 3.6]]}
{"system": [["2024-04-06T17:45:27.742109", 2.2],
["2024-04-06T17:45:28.929983", 2.2]]}
GET limits (used for thresholds)
--------------------------------

View File

@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "GLANCES" "1" "Mar 23, 2024" "4.0.0_beta01" "Glances"
.TH "GLANCES" "1" "Apr 06, 2024" "4.0.0_beta01" "Glances"
.SH NAME
glances \- An eye on your system
.SH SYNOPSIS

View File

@ -33,7 +33,7 @@ class GlancesExport(object):
'now',
'plugin',
'ports',
'processlist',
# 'processlist',
'psutilversion',
'quicklook',
'version',
@ -167,12 +167,12 @@ class GlancesExport(object):
i.update(all_limits[plugin])
else:
continue
export_names, export_values = self.__build_export(all_stats[plugin])
export_names, export_values = self.build_export(all_stats[plugin])
self.export(plugin, export_names, export_values)
return True
def __build_export(self, stats):
def build_export(self, stats):
"""Build the export lists."""
export_names = []
export_values = []
@ -194,7 +194,7 @@ class GlancesExport(object):
except IndexError:
value = ''
if isinstance(value, dict):
item_names, item_values = self.__build_export(value)
item_names, item_values = self.build_export(value)
item_names = [pre_key + key.lower() + str(i) for i in item_names]
export_names += item_names
export_values += item_values
@ -205,7 +205,7 @@ class GlancesExport(object):
# Stats is a list (of dict)
# Recursive loop through the list
for item in stats:
item_names, item_values = self.__build_export(item)
item_names, item_values = self.build_export(item)
export_names += item_names
export_values += item_values
return export_names, export_values

View File

@ -69,7 +69,9 @@ class Export(GlancesExport):
self.csv_file.close()
def update(self, stats):
"""Update stats in the CSV output file."""
"""Update stats in the CSV output file.
Note: This class overwrite the one in the parent class because we need to manage the header.
"""
# Get the stats
all_stats = stats.getAllExportsAsDict(plugin_list=self.plugins_to_export(stats))
@ -80,20 +82,10 @@ class Export(GlancesExport):
# Loop over plugins to export
for plugin in self.plugins_to_export(stats):
if isinstance(all_stats[plugin], list):
for stat in sorted(all_stats[plugin], key=lambda x: x['key']):
# First line: header
if self.first_line:
csv_header += ['{}_{}_{}'.format(plugin, self.get_item_key(stat), item) for item in stat]
# Others lines: stats
csv_data += itervalues(stat)
elif isinstance(all_stats[plugin], dict):
# First line: header
if self.first_line:
fieldnames = iterkeys(all_stats[plugin])
csv_header += ('{}_{}'.format(plugin, fieldname) for fieldname in fieldnames)
# Others lines: stats
csv_data += itervalues(all_stats[plugin])
export_names, export_values = self.build_export(all_stats[plugin])
if self.first_line:
csv_header += export_names
csv_data += export_values
# Export to CSV
# Manage header
@ -117,6 +109,11 @@ class Export(GlancesExport):
self.writer.writerow(csv_data)
self.csv_file.flush()
def export(self, name, columns, points):
"""Export the stats to the CSV file.
For the moment everything is done in the update method."""
pass
def open_csv_file(file_name, file_mode):
return open(file_name, file_mode, newline='')

View File

@ -12,6 +12,49 @@ import re
from glances.logger import logger
class GlancesFilterList(object):
"""Manage a lis of GlancesFilter objects
>>> fl = GlancesFilterList()
>>> fl.filter = '.*python.*,user:nicolargo'
>>> fl.is_filtered({'name': 'python is in the place'})
True
>>> fl.is_filtered({'name': 'snake is in the place'})
False
>>> fl.is_filtered({'name': 'snake is in the place', 'username': 'nicolargo'})
True
>>> fl.is_filtered({'name': 'snake is in the place', 'username': 'notme'})
False
"""
def __init__(self):
self._filter = []
@property
def filter(self):
"""Return the current filter to be applied"""
return self._filter
@filter.setter
def filter(self, value):
"""Add a comma separated list of filters"""
for f in value.split(','):
self._add_filter(f)
def _add_filter(self, filter_input):
"""Add a filter"""
f = GlancesFilter()
f.filter = filter_input
self._filter.append(f)
def is_filtered(self, process):
"""Return True if the process is filtered by at least one filter"""
for f in self._filter:
if f.is_filtered(process):
return True
return False
class GlancesFilter(object):
"""Allow Glances to filter processes
@ -20,13 +63,13 @@ class GlancesFilter(object):
>>> f.filter = '.*python.*'
>>> f.filter
'.*python.*'
>>> f.key
>>> f.filter_key
None
>>> f.filter = 'user:nicolargo'
>>> f.filter = 'username:nicolargo'
>>> f.filter
'nicolargo'
>>> f.key
'user'
>>> f.filter_key
'username'
>>> f.filter = 'username:.*nico.*'
>>> f.filter
'.*nico.*'
@ -60,9 +103,9 @@ class GlancesFilter(object):
"""Set the filter (as a string) and compute the regular expression
A filter could be one of the following:
- python > Process name of cmd start with python
- .*python.* > Process name of cmd contain python
- username:nicolargo > Process of nicolargo user
- python > Process name start with python
- .*python.* > Process name contain python
- user:nicolargo > Process belong to nicolargo user
"""
self._filter_input = value
if value is None:
@ -79,7 +122,9 @@ class GlancesFilter(object):
self._filter_re = None
if self.filter is not None:
logger.info("Set filter to {} on key {}".format(self.filter, self.filter_key))
logger.debug("Set filter to {} on {}".format(
self.filter,
self.filter_key if self.filter_key else 'name or cmdline'))
# Compute the regular expression
try:
self._filter_re = re.compile(self.filter)
@ -123,7 +168,9 @@ class GlancesFilter(object):
try:
# If the item process[key] is a list, convert it to a string
# in order to match it with the current regular expression
if isinstance(process[key], list):
if isinstance(process[key], list) and key == 'cmdline' and len(process[key]) > 0:
value = process[key][0]
elif isinstance(process[key], list):
value = ' '.join(process[key])
else:
value = process[key]
@ -131,7 +178,7 @@ class GlancesFilter(object):
# If the key did not exist
return False
try:
return self._filter_re.fullmatch(value) is None
return self._filter_re.fullmatch(value) is not None
except (AttributeError, TypeError):
# AttributeError - Filter processes crashes with a bad regular expression pattern (issue #665)
# TypeError - Filter processes crashes if value is None (issue #1105)

View File

@ -294,6 +294,13 @@ Examples of use:
dest='export_graph_path',
help='Folder for Graph exporter',
)
parser.add_argument(
'--export-process-filter',
default=None,
type=str,
dest='export_process_filter',
help='set the export process filter (comman separated list of regular expression)',
)
# Client/Server option
parser.add_argument(
'-c', '--client', dest='client', help='connect to a Glances server by IPv4/IPv6 address or hostname'
@ -600,6 +607,10 @@ Examples of use:
args.network_sum = False
args.network_cumul = False
# Processlist id updated in processcount
if getattr(args, 'enable_processlist', False):
enable(args, 'processcount')
def init_client_server(self, args):
"""Init Glances client/server mode."""

View File

@ -392,7 +392,10 @@ class GlancesPluginModel(object):
return self.stats
def get_export(self):
"""Return the stats object to export."""
"""Return the stats object to export.
By default, return the raw stats.
Note: this method could be overwritten by the plugin if a specific format is needed (ex: processlist)
"""
return self.get_raw()
def get_stats(self):

View File

@ -179,14 +179,19 @@ class PluginModel(GlancesPluginModel):
self.pid_max = glances_processes.pid_max
# Set the default sort key if it is defined in the configuration file
if config is not None:
if 'processlist' in config.as_dict() and 'sort_key' in config.as_dict()['processlist']:
if config is not None and 'processlist' in config.as_dict():
if 'sort_key' in config.as_dict()['processlist']:
logger.debug(
'Configuration overwrites processes sort key by {}'.format(
config.as_dict()['processlist']['sort_key']
)
)
glances_processes.set_sort_key(config.as_dict()['processlist']['sort_key'], False)
if 'export' in config.as_dict()['processlist']:
glances_processes.export_process_filter = config.as_dict()['processlist']['export']
if args.export:
logger.info("Export process filter is set to: {}".format(
config.as_dict()['processlist']['export']))
# The default sort key could also be overwrite by command line (see #1903)
if args.sort_processes_key is not None:
@ -226,6 +231,13 @@ class PluginModel(GlancesPluginModel):
return stats
def get_export(self):
"""Return the processes list to export.
Not all the processeses are exported.
Only the one defined in the Glances configuration file (see #794 for details).
"""
return glances_processes.get_export()
def get_nice_alert(self, value):
"""Return the alert relative to the Nice configuration list"""
value = str(value)

View File

@ -12,7 +12,7 @@ import os
from glances.globals import BSD, LINUX, MACOS, WINDOWS, iterkeys
from glances.globals import namedtuple_to_dict, list_of_namedtuple_to_list_of_dict
from glances.timer import Timer, getTimeSinceLastUpdate
from glances.filter import GlancesFilter
from glances.filter import GlancesFilterList, GlancesFilter
from glances.programs import processes_to_programs
from glances.logger import logger
@ -69,6 +69,11 @@ class GlancesProcesses(object):
# Cache is a dict with key=pid and value = dict of cached value
self.processlist_cache = {}
# List of processes stats to export
# Only process matching one of the filter will be exported
self._filter_export = GlancesFilterList()
self.processlist_export = []
# Tag to enable/disable the processes stats (to reduce the Glances CPU consumption)
# Default is to enable the processes stats
self.disable_tag = False
@ -102,7 +107,7 @@ class GlancesProcesses(object):
# Maximum number of processes showed in the UI (None if no limit)
self._max_processes = None
# Process filter is a regular expression
# Process filter
self._filter = GlancesFilter()
# Whether or not to hide kernel threads
@ -202,6 +207,8 @@ class GlancesProcesses(object):
"""Set the maximum number of processes showed in the UI."""
self._max_processes = value
# Process filter
@property
def process_filter_input(self):
"""Get the process filter (given by the user)."""
@ -227,6 +234,20 @@ class GlancesProcesses(object):
"""Get the process regular expression compiled."""
return self._filter.filter_re
# Export filter
@property
def export_process_filter(self):
"""Get the export process filter (current export process filter list)."""
return self._filter_export.filter
@export_process_filter.setter
def export_process_filter(self, value):
"""Set the export process filter list."""
self._filter_export.filter = value
# Kernel threads
def disable_kernel_threads(self):
"""Ignore kernel threads in process list."""
self.no_kernel_threads = True
@ -482,11 +503,11 @@ class GlancesProcesses(object):
except KeyError:
pass
# Apply user filter
processlist = list(filter(lambda p: not self._filter.is_filtered(p), processlist))
# Filter and transform process export list
self.processlist_export = self.update_export_list(processlist)
# Save the new processlist and transform all namedtuples to dict
processlist = list_of_namedtuple_to_list_of_dict(processlist)
# Filter and transform process list
processlist = self.update_list(processlist)
# Compute the maximum value for keys in self._max_values_list: CPU, MEM
# Useful to highlight the processes with maximum values
@ -500,6 +521,22 @@ class GlancesProcesses(object):
return self.processlist
def update_list(self, processlist):
"""Return the process list after filtering and transformation (namedtuple to dict)."""
if self._filter.filter is None:
return list_of_namedtuple_to_list_of_dict(processlist)
ret = list(filter(lambda p: self._filter.is_filtered(p),
processlist))
return list_of_namedtuple_to_list_of_dict(ret)
def update_export_list(self, processlist):
"""Return the process export list after filtering and transformation (namedtuple to dict)."""
if self._filter_export.filter == []:
return []
ret = list(filter(lambda p: self._filter_export.is_filtered(p),
processlist))
return list_of_namedtuple_to_list_of_dict(ret)
def get_count(self):
"""Get the number of processes."""
return self.processcount
@ -513,6 +550,10 @@ class GlancesProcesses(object):
else:
return self.processlist
def get_export(self):
"""Return the processlist for export."""
return self.processlist_export
@property
def sort_key(self):
"""Get the current sort key."""

View File

@ -64,8 +64,13 @@ class GlancesStandalone(object):
# Manage optional process filter
if args.process_filter is not None:
logger.info("Process filter is set to: {}".format(args.process_filter))
glances_processes.process_filter = args.process_filter
if args.export and args.export_process_filter is not None:
logger.info("Export process filter is set to: {}".format(args.export_process_filter))
glances_processes.export_process_filter = args.export_process_filter
if (not WINDOWS) and args.no_kernel_threads:
# Ignore kernel threads in process list
glances_processes.disable_kernel_threads()

View File

@ -304,10 +304,10 @@ class GlancesStats(object):
return {p: self._plugins[p].get_raw() for p in self._plugins}
def getAllExports(self, plugin_list=None):
"""Return all the stats to be exported (list).
"""Return all the stats to be exported as a list.
Default behavior is to export all the stat
if plugin_list is provided, only export stats of given plugin (list)
if plugin_list is provided (list), only export stats of given plugins
"""
if plugin_list is None:
# All enabled plugins should be exported
@ -315,10 +315,10 @@ class GlancesStats(object):
return [self._plugins[p].get_export() for p in self._plugins]
def getAllExportsAsDict(self, plugin_list=None):
"""Return all the stats to be exported (list).
"""Return all the stats to be exported as a dict.
Default behavior is to export all the stat
if plugin_list is provided, only export stats of given plugin (list)
if plugin_list is provided (list), only export stats of given plugins
"""
if plugin_list is None:
# All enabled plugins should be exported

View File

@ -33,6 +33,7 @@ from glances.plugins.plugin.model import GlancesPluginModel
from glances.programs import processes_to_programs
from glances.secure import secure_popen
from glances.events_list import GlancesEventsList
from glances.filter import GlancesFilterList, GlancesFilter
# Global variables
# =================
@ -345,6 +346,30 @@ class TestGlances(unittest.TestCase):
events.clean()
self.assertEqual(len(events.get()), 1)
def test_020_filter(self):
"""Test filter classes"""
print('INFO: [TEST_020] Test filter')
gf = GlancesFilter()
gf.filter = '.*python.*'
self.assertEqual(gf.filter, '.*python.*')
self.assertEqual(gf.filter_key, None)
self.assertTrue(gf.is_filtered({'name': 'python'}))
self.assertTrue(gf.is_filtered({'name': '/usr/bin/python -m glances'}))
self.assertFalse(gf.is_filtered({'noname': 'python'}))
self.assertFalse(gf.is_filtered({'name': 'snake'}))
gf.filter = 'username:nicolargo'
self.assertEqual(gf.filter, 'nicolargo')
self.assertEqual(gf.filter_key, 'username')
self.assertTrue(gf.is_filtered({'username': 'nicolargo'}))
self.assertFalse(gf.is_filtered({'username': 'notme'}))
self.assertFalse(gf.is_filtered({'notuser': 'nicolargo'}))
gfl = GlancesFilterList()
gfl.filter = '.*python.*,username:nicolargo'
self.assertTrue(gfl.is_filtered({'name': 'python is in the place'}))
self.assertFalse(gfl.is_filtered({'name': 'snake is in the place'}))
self.assertTrue(gfl.is_filtered({'name': 'snake is in the place', 'username': 'nicolargo'}))
self.assertFalse(gfl.is_filtered({'name': 'snake is in the place', 'username': 'notme'}))
def test_094_thresholds(self):
"""Test thresholds classes"""
print('INFO: [TEST_094] Thresholds')