mirror of https://github.com/nicolargo/glances.git
Merge branch 'issue410' of github.com:nicolargo/glances into issue410
This commit is contained in:
commit
c32d363897
647
docs/api.rst
647
docs/api.rst
File diff suppressed because it is too large
Load Diff
|
|
@ -28,7 +28,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" "Jan 04, 2025" "4.3.0.8" "Glances"
|
||||
.TH "GLANCES" "1" "Jan 26, 2025" "4.3.1_dev09" "Glances"
|
||||
.SH NAME
|
||||
glances \- An eye on your system
|
||||
.SH SYNOPSIS
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ from configparser import ConfigParser, NoOptionError, NoSectionError
|
|||
from datetime import datetime
|
||||
from operator import itemgetter, methodcaller
|
||||
from statistics import mean
|
||||
from typing import Any, Union
|
||||
from typing import Any, Optional, Union
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
|
@ -393,13 +393,22 @@ def dictlist(data, item):
|
|||
return None
|
||||
|
||||
|
||||
def json_dumps_dictlist(data, item):
|
||||
def dictlist_json_dumps(data, item):
|
||||
dl = dictlist(data, item)
|
||||
if dl is None:
|
||||
return None
|
||||
return json_dumps(dl)
|
||||
|
||||
|
||||
def dictlist_first_key_value(data: list[dict], key, value) -> Optional[dict]:
|
||||
"""In a list of dict, return first item where key=value or none if not found."""
|
||||
try:
|
||||
ret = next(item for item in data if key in item and item[key] == value)
|
||||
except StopIteration:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
|
||||
def string_value_to_float(s):
|
||||
"""Convert a string with a value and an unit to a float.
|
||||
Example:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from glances.events_list import glances_events
|
|||
from glances.globals import json_dumps
|
||||
from glances.logger import logger
|
||||
from glances.password import GlancesPassword
|
||||
from glances.processes import glances_processes
|
||||
from glances.servers_list import GlancesServersList
|
||||
from glances.servers_list_dynamic import GlancesAutoDiscoverClient
|
||||
from glances.stats import GlancesStats
|
||||
|
|
@ -221,6 +222,9 @@ class GlancesRestfulApi:
|
|||
# POST
|
||||
router.add_api_route(f'{base_path}/events/clear/warning', self._events_clear_warning, methods=['POST'])
|
||||
router.add_api_route(f'{base_path}/events/clear/all', self._events_clear_all, methods=['POST'])
|
||||
router.add_api_route(
|
||||
f'{base_path}/processes/extended/{{pid}}', self._api_set_extended_processes, methods=['POST']
|
||||
)
|
||||
|
||||
# GET
|
||||
route_mapping = {
|
||||
|
|
@ -235,6 +239,8 @@ class GlancesRestfulApi:
|
|||
f'{base_path}/all/views': self._api_all_views,
|
||||
f'{base_path}/pluginslist': self._api_plugins,
|
||||
f'{base_path}/serverslist': self._api_servers_list,
|
||||
f'{base_path}/processes/extended': self._api_get_extended_processes,
|
||||
f'{base_path}/processes/{{pid}}': self._api_get_processes,
|
||||
f'{plugin_path}': self._api,
|
||||
f'{plugin_path}/history': self._api_history,
|
||||
f'{plugin_path}/history/{{nb}}': self._api_history,
|
||||
|
|
@ -900,3 +906,52 @@ class GlancesRestfulApi:
|
|||
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args item ({str(e)})")
|
||||
|
||||
return GlancesJSONResponse(args_json)
|
||||
|
||||
def _api_set_extended_processes(self, pid: str):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
Set the extended process stats for the given PID
|
||||
HTTP/200 if OK
|
||||
HTTP/400 if PID is not found
|
||||
HTTP/404 if others error
|
||||
"""
|
||||
process_stats = glances_processes.get_stats(int(pid))
|
||||
|
||||
if not process_stats:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Unknown PID process {pid}")
|
||||
|
||||
glances_processes.extended_process = process_stats
|
||||
|
||||
return GlancesJSONResponse(True)
|
||||
|
||||
def _api_get_extended_processes(self):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
Get the extended process stats (if set before)
|
||||
HTTP/200 if OK
|
||||
HTTP/400 if PID is not found
|
||||
HTTP/404 if others error
|
||||
"""
|
||||
process_stats = glances_processes.get_extended_stats()
|
||||
|
||||
if not process_stats:
|
||||
process_stats = {}
|
||||
|
||||
print("Call _api_get_extended_processes")
|
||||
|
||||
return GlancesJSONResponse(process_stats)
|
||||
|
||||
def _api_get_processes(self, pid: str):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
Get the process stats for the given PID
|
||||
HTTP/200 if OK
|
||||
HTTP/400 if PID is not found
|
||||
HTTP/404 if others error
|
||||
"""
|
||||
process_stats = glances_processes.get_stats(int(pid))
|
||||
|
||||
if not process_stats:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Unknown PID process {pid}")
|
||||
|
||||
return GlancesJSONResponse(process_stats)
|
||||
|
|
|
|||
|
|
@ -225,7 +225,30 @@ def print_all():
|
|||
print('Get all Glances stats::')
|
||||
print('')
|
||||
print(f' # curl {API_URL}/all')
|
||||
print(' Return a very big dictionary (avoid using this request, performances will be poor)...')
|
||||
print(' Return a very big dictionary with all stats')
|
||||
print('')
|
||||
print('Note: Update is done automatically every time /all or /<plugin> is called.')
|
||||
print('')
|
||||
|
||||
|
||||
def print_processes():
|
||||
sub_title = 'GET stats of a specific process'
|
||||
print(sub_title)
|
||||
print('-' * len(sub_title))
|
||||
print('')
|
||||
print('Get stats for process with PID == 777::')
|
||||
print('')
|
||||
print(f' # curl {API_URL}/processes/777')
|
||||
print(' Return stats for process (dict)')
|
||||
print('')
|
||||
print('Enable extended stats for process with PID == 777 (only one process at a time can be enabled)::')
|
||||
print('')
|
||||
print(f' # curl -X POST {API_URL}/processes/extended/777')
|
||||
print(f' # curl {API_URL}/all')
|
||||
print(f' # curl {API_URL}/processes/777')
|
||||
print(' Return stats for process (dict)')
|
||||
print('')
|
||||
print('Note: Update *is not* done automatically when you call /processes/<pid>.')
|
||||
print('')
|
||||
|
||||
|
||||
|
|
@ -370,6 +393,9 @@ class GlancesStdoutApiDoc:
|
|||
# Get all stats
|
||||
print_all()
|
||||
|
||||
# Get process stats
|
||||
print_processes()
|
||||
|
||||
# Get top stats (only for plugins with a list of items)
|
||||
# Example for processlist plugin: get top 2 processes
|
||||
print_top(stats)
|
||||
|
|
|
|||
|
|
@ -398,6 +398,11 @@ body {
|
|||
.table {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.table-hover tbody tr:hover td {
|
||||
background: $glances-link-hover-color;
|
||||
}
|
||||
|
||||
// Default column size
|
||||
* > td:nth-child(-n+12) {
|
||||
width: 5em;
|
||||
|
|
@ -427,6 +432,7 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#alerts {
|
||||
span {
|
||||
padding-left: 10px;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
<!-- Display processes -->
|
||||
<section class="plugin" id="processlist" v-if="!args.programs">
|
||||
<div>PIN PROCESS: {{ extended_stat }} - {{ getPinProcess() }}</div>
|
||||
<div class="table-responsive d-lg-none">
|
||||
<table class="table table-sm table-borderless table-striped table-hover">
|
||||
<thead>
|
||||
|
|
@ -31,7 +32,8 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(process, processId) in processes" :key="processId">
|
||||
<tr v-for="(process, processId) in processes" :key="processId" @click="setPinProcess(process)"
|
||||
style="cursor: pointer">
|
||||
<td scope="row" :class="getCpuPercentAlert(process)"
|
||||
v-show="!getDisableStats().includes('cpu_percent')">
|
||||
{{ process.cpu_percent == -1 ? '?' : $filters.number(process.cpu_percent, 1) }}
|
||||
|
|
@ -114,7 +116,8 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(process, processId) in processes" :key="processId">
|
||||
<tr v-for="(process, processId) in processes" :key="processId" @click="setPinProcess(process.pid)"
|
||||
style="cursor: pointer">
|
||||
<td scope="row" :class="getCpuPercentAlert(process)"
|
||||
v-show="!getDisableStats().includes('cpu_percent')">
|
||||
{{ process.cpu_percent == -1 ? '?' : $filters.number(process.cpu_percent, 1) }}
|
||||
|
|
@ -354,9 +357,20 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
store
|
||||
store,
|
||||
extended_stat: undefined,
|
||||
intervalId: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// Refresh every second
|
||||
this.intervalId = setInterval(() => {
|
||||
this.getPinProcess()
|
||||
}, 1000)
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearInterval(this.intervalId)
|
||||
},
|
||||
computed: {
|
||||
args() {
|
||||
return this.store.args || {};
|
||||
|
|
@ -527,7 +541,7 @@ export default {
|
|||
return this.config.outputs !== undefined
|
||||
? this.config.outputs.max_processes_display
|
||||
: undefined;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getCpuPercentAlert(process) {
|
||||
|
|
@ -538,6 +552,15 @@ export default {
|
|||
},
|
||||
getDisableStats() {
|
||||
return GlancesHelper.getLimit('processlist', 'processlist_disable_stats') || [];
|
||||
},
|
||||
setPinProcess(pid) {
|
||||
fetch('api/4/processes/extended/' + pid.toString(), { method: 'POST' })
|
||||
.then((response) => response.json());
|
||||
},
|
||||
getPinProcess() {
|
||||
fetch('api/4/processes/extended', { method: 'GET' })
|
||||
.then((response) => response.json())
|
||||
.then((response) => (this.extended_stat = response));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -17,7 +17,7 @@ import re
|
|||
|
||||
from glances.actions import GlancesActions
|
||||
from glances.events_list import glances_events
|
||||
from glances.globals import dictlist, iterkeys, itervalues, json_dumps, json_dumps_dictlist, listkeys, mean, nativestr
|
||||
from glances.globals import dictlist, dictlist_json_dumps, iterkeys, itervalues, json_dumps, listkeys, mean, nativestr
|
||||
from glances.history import GlancesHistory
|
||||
from glances.logger import logger
|
||||
from glances.outputs.glances_unicode import unicode_message
|
||||
|
|
@ -244,7 +244,7 @@ class GlancesPluginModel:
|
|||
if item is None:
|
||||
return json_dumps(s)
|
||||
|
||||
return json_dumps_dictlist(s, item)
|
||||
return dictlist_json_dumps(s, item)
|
||||
|
||||
def get_trend(self, item, nb=30):
|
||||
"""Get the trend regarding to the last nb values.
|
||||
|
|
@ -405,7 +405,7 @@ class GlancesPluginModel:
|
|||
|
||||
Stats should be a list of dict (processlist, network...)
|
||||
"""
|
||||
return json_dumps_dictlist(self.get_raw(), item)
|
||||
return dictlist_json_dumps(self.get_raw(), item)
|
||||
|
||||
def get_raw_stats_value(self, item, value):
|
||||
"""Return the stats object for a specific item=value.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,16 @@ import os
|
|||
import psutil
|
||||
|
||||
from glances.filter import GlancesFilter, GlancesFilterList
|
||||
from glances.globals import BSD, LINUX, MACOS, WINDOWS, iterkeys, list_of_namedtuple_to_list_of_dict, namedtuple_to_dict
|
||||
from glances.globals import (
|
||||
BSD,
|
||||
LINUX,
|
||||
MACOS,
|
||||
WINDOWS,
|
||||
dictlist_first_key_value,
|
||||
iterkeys,
|
||||
list_of_namedtuple_to_list_of_dict,
|
||||
namedtuple_to_dict,
|
||||
)
|
||||
from glances.logger import logger
|
||||
from glances.programs import processes_to_programs
|
||||
from glances.timer import Timer, getTimeSinceLastUpdate
|
||||
|
|
@ -303,8 +312,8 @@ class GlancesProcesses:
|
|||
for k in self._max_values_list:
|
||||
self._max_values[k] = 0.0
|
||||
|
||||
def get_extended_stats(self, proc):
|
||||
"""Get the extended stats for the given PID."""
|
||||
def set_extended_stats(self, proc):
|
||||
"""Set the extended stats for the given PID."""
|
||||
# - cpu_affinity (Linux, Windows, FreeBSD)
|
||||
# - ionice (Linux and Windows > Vista)
|
||||
# - num_ctx_switches (not available on Illumos/Solaris)
|
||||
|
|
@ -350,6 +359,16 @@ class GlancesProcesses:
|
|||
ret['extended_stats'] = True
|
||||
return namedtuple_to_dict(ret)
|
||||
|
||||
def get_extended_stats(self):
|
||||
"""Return the extended stats.
|
||||
|
||||
Return the process stat when extended_stats = True
|
||||
"""
|
||||
for p in self.processlist:
|
||||
if p.get('extended_stats'):
|
||||
return p
|
||||
return None
|
||||
|
||||
def __get_min_max_mean(self, proc, prefix=['cpu', 'memory']):
|
||||
"""Return the min/max/mean for the given process"""
|
||||
ret = {}
|
||||
|
|
@ -578,7 +597,7 @@ class GlancesProcesses:
|
|||
|
||||
# Grab extended stats only for the selected process (see issue #2225)
|
||||
if self.extended_process is not None and proc['pid'] == self.extended_process['pid']:
|
||||
proc.update(self.get_extended_stats(self.extended_process))
|
||||
proc.update(self.set_extended_stats(self.extended_process))
|
||||
self.extended_process = namedtuple_to_dict(proc)
|
||||
|
||||
# Meta data
|
||||
|
|
@ -652,6 +671,10 @@ class GlancesProcesses:
|
|||
"""Return the processlist for export."""
|
||||
return self.processlist_export
|
||||
|
||||
def get_stats(self, pid):
|
||||
"""Get stats for the given pid."""
|
||||
return dictlist_first_key_value(self.processlist, 'pid', pid)
|
||||
|
||||
@property
|
||||
def sort_key(self):
|
||||
"""Get the current sort key."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue