diff --git a/NEWS b/NEWS index c67fa49c..2228d9b4 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ Enhancements and news features: * Grab FAN speed in the Glances sensors plugin (issue #501) * Allow logical mounts points in the FS plugin (issue #448) + * Add a --disable-hddtemp to disable HDD temperature module at startup (issue #515) + * Add a quiet mode (-q). Can be usefull if you need a Statsd or Influxdb provider only. Bugs corrected: diff --git a/README.rst b/README.rst index e7c28670..93b603c7 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,7 @@ It uses the `psutil`_ library to get information from your system. Requirements ============ -- ``python >= 2.6`` (tested with version 2.6, 2.7, 3.3, 3.4) +- ``python >= 2.6`` or ``>= 3.3`` (tested with version 2.6, 2.7, 3.3, 3.4) - ``psutil >= 2.0.0`` - ``setuptools`` diff --git a/conf/glances-grafana.json b/conf/glances-grafana.json new file mode 100644 index 00000000..89152238 --- /dev/null +++ b/conf/glances-grafana.json @@ -0,0 +1,1140 @@ +{ + "id": null, + "title": "Glances", + "originalTitle": "Glances", + "tags": [], + "style": "dark", + "timezone": "browser", + "editable": true, + "hideControls": false, + "sharedCrosshair": false, + "rows": [ + { + "title": "test", + "height": "250px", + "editable": true, + "collapse": false, + "panels": [ + { + "title": "Core", + "error": false, + "span": 1, + "editable": true, + "type": "singlestat", + "id": 5, + "links": [], + "maxDataPoints": 100, + "interval": null, + "targets": [ + { + "function": "mean", + "column": "cpucore", + "series": "load", + "query": "select mean(cpucore) from \"load\" where $timeFilter group by time($interval) order asc" + } + ], + "cacheTimeout": null, + "format": "none", + "prefix": "", + "postfix": "", + "nullText": null, + "valueMaps": [ + { + "value": "null", + "op": "=", + "text": "N/A" + } + ], + "nullPointMode": "connected", + "valueName": "avg", + "prefixFontSize": "50%", + "valueFontSize": "80%", + "postfixFontSize": "50%", + "thresholds": "", + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "sparkline": { + "show": false, + "full": false, + "lineColor": "rgb(31, 120, 193)", + "fillColor": "rgba(31, 118, 189, 0.18)" + }, + "datasource": "glances" + }, + { + "id": 4, + "span": 10, + "type": "graph", + "x-axis": true, + "y-axis": true, + "scale": 1, + "y_formats": [ + "short", + "short" + ], + "grid": { + "max": null, + "min": null, + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)", + "thresholdLine": false + }, + "resolution": 100, + "lines": true, + "fill": 1, + "linewidth": 2, + "points": false, + "pointradius": 5, + "bars": false, + "stack": true, + "spyable": true, + "options": false, + "legend": { + "show": true, + "values": true, + "min": true, + "max": true, + "current": false, + "total": false, + "avg": true, + "rightSide": false, + "alignAsTable": false + }, + "interactive": true, + "legend_counts": true, + "timezone": "browser", + "percentage": false, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "query_as_alias": true, + "shared": false + }, + "targets": [ + { + "target": "randomWalk('random walk')", + "function": "mean", + "column": "min1", + "series": "load", + "query": "select mean(min1) from \"load\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "1min", + "fill": "null" + }, + { + "target": "", + "function": "mean", + "column": "min5", + "series": "load", + "query": "select mean(min5) from \"load\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "5mins", + "fill": "null" + }, + { + "target": "", + "function": "mean", + "column": "min15", + "series": "load", + "query": "select mean(min15) from \"load\" where $timeFilter group by time($interval) fill(null) order asc", + "fill": "null", + "alias": "15mins" + } + ], + "aliasColors": {}, + "aliasYAxis": {}, + "title": "Load", + "datasource": "glances", + "renderer": "flot", + "annotate": { + "enable": false + }, + "seriesOverrides": [], + "links": [] + }, + { + "title": "Processes", + "error": false, + "span": 1, + "editable": true, + "type": "singlestat", + "id": 18, + "links": [], + "maxDataPoints": 100, + "interval": null, + "targets": [ + { + "function": "mean", + "column": "total", + "series": "processcount", + "query": "select mean(total) from \"processcount\" where $timeFilter group by time($interval) order asc" + } + ], + "cacheTimeout": null, + "format": "none", + "prefix": "", + "postfix": "", + "nullText": null, + "valueMaps": [ + { + "value": "null", + "op": "=", + "text": "N/A" + } + ], + "nullPointMode": "connected", + "valueName": "avg", + "prefixFontSize": "50%", + "valueFontSize": "80%", + "postfixFontSize": "50%", + "thresholds": "", + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "sparkline": { + "show": true, + "full": false, + "lineColor": "rgb(31, 120, 193)", + "fillColor": "rgba(31, 118, 189, 0.18)" + } + } + ] + }, + { + "title": "New row", + "height": "250px", + "editable": true, + "collapse": false, + "panels": [ + { + "title": "CPU (%)", + "error": false, + "span": 6, + "editable": true, + "type": "graph", + "id": 6, + "datasource": "glances", + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "percent", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": true, + "max": true, + "current": false, + "total": false, + "avg": true, + "rightSide": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "user", + "series": "cpu", + "query": "select mean(user) from \"cpu\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "User", + "fill": "null" + }, + { + "target": "", + "function": "mean", + "column": "system", + "series": "cpu", + "query": "select mean(system) from \"cpu\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "System", + "fill": "null" + }, + { + "target": "", + "function": "mean", + "column": "iowait", + "series": "cpu", + "query": "select mean(iowait) from \"cpu\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "IoWait", + "fill": "null" + } + ], + "aliasColors": {}, + "seriesOverrides": [], + "links": [] + }, + { + "title": "MEM", + "error": false, + "span": 6, + "editable": true, + "type": "graph", + "id": 7, + "datasource": "glances", + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "bytes", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "used", + "series": "mem", + "query": "select mean(used) from \"mem\" where $timeFilter group by time($interval) order asc", + "alias": "Used" + }, + { + "target": "", + "function": "mean", + "column": "total", + "series": "mem", + "query": "select mean(total) from \"mem\" where $timeFilter group by time($interval) order asc", + "alias": "Max" + } + ], + "aliasColors": {}, + "seriesOverrides": [], + "links": [] + } + ] + }, + { + "title": "New row", + "height": "250px", + "editable": true, + "collapse": false, + "panels": [ + { + "title": "Net (Enp0s25)", + "error": false, + "span": 6, + "editable": true, + "type": "graph", + "id": 9, + "datasource": "glances", + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "bps", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 3, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "enp0s25.rx", + "series": "network", + "query": "select mean(enp0s25.rx) from \"network\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "Rx", + "interval": "", + "fill": "null" + }, + { + "target": "", + "function": "mean", + "column": "enp0s25.tx*-1", + "series": "network", + "query": "select mean(enp0s25.tx*-1) from \"network\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "Tx", + "fill": "null" + } + ], + "aliasColors": {}, + "seriesOverrides": [], + "links": [] + }, + { + "title": "SWAP", + "error": false, + "span": 6, + "editable": true, + "type": "graph", + "id": 8, + "datasource": "glances", + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "bytes", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "used", + "series": "memswap", + "query": "select mean(used) from \"memswap\" where $timeFilter group by time($interval) order asc", + "alias": "Used" + }, + { + "target": "", + "function": "mean", + "column": "total", + "series": "memswap", + "query": "select mean(total) from \"memswap\" where $timeFilter group by time($interval) order asc", + "alias": "Max" + } + ], + "aliasColors": {}, + "seriesOverrides": [], + "links": [] + } + ] + }, + { + "title": "New row", + "height": "250px", + "editable": true, + "collapse": false, + "panels": [ + { + "title": "sda2 disk IO", + "error": false, + "span": 6, + "editable": true, + "type": "graph", + "id": 10, + "datasource": "glances", + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "bytes", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "sda2.read_bytes", + "series": "diskio", + "query": "select mean(sda2.read_bytes) from \"diskio\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "Read", + "fill": "null" + }, + { + "target": "", + "function": "mean", + "column": "sda2.write_bytes", + "series": "diskio", + "query": "select mean(sda2.write_bytes) from \"diskio\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "Write", + "fill": "null" + } + ], + "aliasColors": {}, + "seriesOverrides": [], + "links": [] + }, + { + "title": "/ Size", + "error": false, + "span": 4, + "editable": true, + "type": "graph", + "id": 11, + "datasource": "glances", + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "bytes", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "\"/.used\"", + "series": "fs", + "query": "select mean(\"/.used\") from \"fs\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "Used", + "fill": "null" + }, + { + "target": "", + "function": "mean", + "column": "\"/.size\"", + "series": "fs", + "query": "select mean(\"/.size\") from \"fs\" where $timeFilter group by time($interval) fill(null) order asc", + "alias": "Max", + "fill": "null" + } + ], + "aliasColors": {}, + "seriesOverrides": [], + "links": [] + }, + { + "title": "/ used", + "error": false, + "span": 1, + "editable": true, + "type": "singlestat", + "id": 16, + "links": [], + "maxDataPoints": 100, + "interval": null, + "targets": [ + { + "function": "mean", + "column": "\"/.percent\"", + "series": "fs", + "query": "select mean(\"/.percent\") from \"fs\" where $timeFilter group by time($interval) order asc" + } + ], + "cacheTimeout": null, + "format": "percent", + "prefix": "", + "postfix": "", + "nullText": null, + "valueMaps": [ + { + "value": "null", + "op": "=", + "text": "N/A" + } + ], + "nullPointMode": "connected", + "valueName": "avg", + "prefixFontSize": "50%", + "valueFontSize": "80%", + "postfixFontSize": "50%", + "thresholds": "0,70,90", + "colorBackground": true, + "colorValue": false, + "colors": [ + "rgba(71, 212, 59, 0.4)", + "rgba(245, 150, 40, 0.73)", + "rgba(225, 40, 40, 0.59)" + ], + "sparkline": { + "show": true, + "full": false, + "lineColor": "rgb(193, 71, 31)", + "fillColor": "rgba(31, 118, 189, 0.18)" + } + }, + { + "title": "/home used", + "error": false, + "span": 1, + "editable": true, + "type": "singlestat", + "id": 17, + "links": [], + "maxDataPoints": 100, + "interval": null, + "targets": [ + { + "function": "mean", + "column": "\"/home.percent\"", + "series": "fs", + "query": "select mean(\"/home.percent\") from \"fs\" where $timeFilter group by time($interval) order asc" + } + ], + "cacheTimeout": null, + "format": "percent", + "prefix": "", + "postfix": "", + "nullText": null, + "valueMaps": [ + { + "value": "null", + "op": "=", + "text": "N/A" + } + ], + "nullPointMode": "connected", + "valueName": "avg", + "prefixFontSize": "50%", + "valueFontSize": "80%", + "postfixFontSize": "50%", + "thresholds": "0,70,90", + "colorBackground": true, + "colorValue": false, + "colors": [ + "rgba(71, 212, 59, 0.4)", + "rgba(245, 150, 40, 0.73)", + "rgba(225, 40, 40, 0.59)" + ], + "sparkline": { + "show": true, + "full": false, + "lineColor": "rgb(193, 71, 31)", + "fillColor": "rgba(31, 118, 189, 0.18)" + } + } + ] + }, + { + "title": "New row", + "height": "25px", + "editable": true, + "collapse": false, + "panels": [ + { + "title": "CPU details", + "error": false, + "span": 12, + "editable": true, + "type": "text", + "id": 13, + "mode": "text", + "content": "", + "style": {}, + "links": [] + } + ] + }, + { + "title": "New row", + "height": "250px", + "editable": true, + "collapse": false, + "panels": [ + { + "title": "CPU user", + "error": false, + "span": 12, + "editable": true, + "type": "graph", + "id": 12, + "datasource": null, + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "short", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "user", + "series": "cpu", + "query": "select mean(user) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "mean" + }, + { + "target": "", + "function": "min", + "column": "user", + "series": "cpu", + "query": "select min(user) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "min" + }, + { + "target": "", + "function": "max", + "column": "user", + "series": "cpu", + "query": "select max(user) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "max" + } + ], + "aliasColors": { + "max": "#890F02" + }, + "seriesOverrides": [ + { + "alias": "max", + "fillBelowTo": "min", + "lines": false + }, + { + "alias": "min", + "lines": false + }, + { + "alias": "mean", + "zindex": 3, + "linewidth": 2 + } + ], + "links": [] + }, + { + "title": "CPU system", + "error": false, + "span": 12, + "editable": true, + "type": "graph", + "id": 14, + "datasource": null, + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "short", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "system", + "series": "cpu", + "query": "select mean(system) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "mean" + }, + { + "target": "", + "function": "min", + "column": "system", + "series": "cpu", + "query": "select min(system) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "min" + }, + { + "target": "", + "function": "max", + "column": "system", + "series": "cpu", + "query": "select max(system) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "max" + } + ], + "aliasColors": { + "max": "#890F02" + }, + "seriesOverrides": [ + { + "alias": "max", + "fillBelowTo": "min", + "lines": false + }, + { + "alias": "min", + "lines": false + }, + { + "alias": "mean", + "zindex": 3, + "linewidth": 2 + } + ], + "links": [] + }, + { + "title": "CPU iowait", + "error": false, + "span": 12, + "editable": true, + "type": "graph", + "id": 15, + "datasource": null, + "renderer": "flot", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "short", + "short" + ], + "grid": { + "leftMax": null, + "rightMax": null, + "leftMin": null, + "rightMin": null, + "threshold1": null, + "threshold2": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "lines": true, + "fill": 0, + "linewidth": 1, + "points": false, + "pointradius": 5, + "bars": false, + "stack": false, + "percentage": false, + "legend": { + "show": true, + "values": false, + "min": false, + "max": false, + "current": false, + "total": false, + "avg": false + }, + "nullPointMode": "connected", + "steppedLine": false, + "tooltip": { + "value_type": "cumulative", + "shared": false + }, + "targets": [ + { + "function": "mean", + "column": "iowait", + "series": "cpu", + "query": "select mean(iowait) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "mean" + }, + { + "target": "", + "function": "min", + "column": "iowait", + "series": "cpu", + "query": "select min(iowait) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "min" + }, + { + "target": "", + "function": "max", + "column": "iowait", + "series": "cpu", + "query": "select max(iowait) from \"cpu\" where $timeFilter group by time($interval) order asc", + "interval": "60s", + "alias": "max" + } + ], + "aliasColors": { + "max": "#890F02" + }, + "seriesOverrides": [ + { + "alias": "max", + "fillBelowTo": "min", + "lines": false + }, + { + "alias": "min", + "lines": false + }, + { + "alias": "mean", + "zindex": 3, + "linewidth": 2 + } + ], + "links": [] + } + ] + } + ], + "nav": [ + { + "type": "timepicker", + "collapse": false, + "enable": true, + "status": "Stable", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ], + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "now": true, + "notice": false + } + ], + "time": { + "from": "now-12h", + "to": "now" + }, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "refresh": false, + "version": 6, + "hideAllLegends": false +} \ No newline at end of file diff --git a/conf/glances-test.conf b/conf/glances-test.conf index ff740d5a..f68813af 100644 --- a/conf/glances-test.conf +++ b/conf/glances-test.conf @@ -180,6 +180,7 @@ port=8086 user=root password=root db=glances +#prefix=localhost [statsd] host=localhost diff --git a/conf/glances.conf b/conf/glances.conf index 9a56d565..b1e720a4 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -171,6 +171,7 @@ port=8086 user=root password=root db=glances +#prefix=localhost [statsd] host=localhost diff --git a/docs/glances-doc.rst b/docs/glances-doc.rst index 656bc4bf..16bd1b04 100644 --- a/docs/glances-doc.rst +++ b/docs/glances-doc.rst @@ -2,11 +2,11 @@ Glances ======= -This manual describes *Glances* version 2.3. +This manual describes *Glances* version 2.4. Copyright © 2011-2015 Nicolas Hennion -January 2015 +April 2015 .. contents:: Table of Contents @@ -143,6 +143,7 @@ Command-Line Options --disable-fs disable filesystem module --disable-network disable network module --disable-sensors disable sensors module + --disable-hddtemp disable HDDTemp module --disable-left-sidebar disable left sidebar --disable-process disable process module @@ -187,6 +188,7 @@ Command-Line Options --snmp-force force SNMP mode -t TIME, --time TIME set refresh time in seconds [default: 3 sec] -w, --webserver run Glances in web server mode + -q, --quiet run Glances in quiet mode (nothing is displayed) -f PROCESS_FILTER, --process-filter PROCESS_FILTER set the process filter patern (regular expression) --process-short-name force short name for processes name @@ -397,7 +399,7 @@ Disconnected: .. image:: images/disconnected.png -QUICKLOOK +QuickLook --------- The quicklook plugin is only display on wide screen and propose a bar view for CPU and memory (virtual and swap). @@ -789,6 +791,10 @@ and run Glances with: $ glances --export-influxdb +For Grafana users', Glances provides a dedicated `dashboard`_. Just import the file in your Grafana Web interface. + +.. image:: images/grafana.png + *Statsd* You can export statistics to a Statsd server (welcome to Graphite !). The connection should be defined in the Glances configuration file as following: @@ -847,3 +853,4 @@ Feel free to contribute ! .. _XML-RPC server: http://docs.python.org/2/library/simplexmlrpcserver.html .. _RESTFUL-JSON: http://jsonapi.org/ .. _forum: https://groups.google.com/forum/?hl=en#!forum/glances-users +.. _forum: https://github.com/nicolargo/glances/blob/master/conf/glances-grafana.json diff --git a/docs/images/grafana.png b/docs/images/grafana.png new file mode 100644 index 00000000..cd1602e3 Binary files /dev/null and b/docs/images/grafana.png differ diff --git a/glances/__init__.py b/glances/__init__.py index e6374f29..e9571592 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -44,21 +44,17 @@ from glances.core.glances_globals import gettext_domain, locale_dir from glances.core.glances_logging import logger from glances.core.glances_main import GlancesMain -# Get PSutil version -psutil_min_version = (2, 0, 0) -psutil_version = tuple([int(num) for num in __psutil_version.split('.')]) - -# First log with Glances and PSUtil version -logger.info('Start Glances {0}'.format(__version__)) -logger.info('{0} {1} and PSutil {2} detected'.format(platform.python_implementation(), - platform.python_version(), - __psutil_version)) - -# Check PSutil version -if psutil_version < psutil_min_version: - logger.critical('PSutil 2.0 or higher is needed. Glances cannot start.') +# Check Python version +if sys.version_info < (2, 6) or (3, 0) <= sys.version_info < (3, 3): + print('Glances requires at least Python 2.6 or 3.3 to run.') sys.exit(1) +# Check PSutil version +psutil_min_version = (2, 0, 0) +psutil_version = tuple([int(num) for num in __psutil_version.split('.')]) +if psutil_version < psutil_min_version: + print('PSutil 2.0 or higher is needed. Glances cannot start.') + sys.exit(1) def __signal_handler(signal, frame): """Callback for CTRL-C.""" @@ -94,8 +90,23 @@ def main(): Select the mode (standalone, client or server) Run it... """ + # Log Glances and PSutil version + logger.info('Start Glances {0}'.format(__version__)) + logger.info('{0} {1} and PSutil {2} detected'.format( + platform.python_implementation(), + platform.python_version(), + __psutil_version)) + # Setup translations - locale.setlocale(locale.LC_ALL, '') + try: + locale.setlocale(locale.LC_ALL, '') + except locale.Error: + # Issue #517 + # Setting LC_ALL to '' should not generate an error unless LC_ALL is not + # defined in the user environment, which can be the case when used via SSH. + # So simply skip this error, as python will use the C locale by default. + logger.warning("No locale LC_ALL variable found. Use the default C locale.") + pass gettext.install(gettext_domain, locale_dir) # Share global var diff --git a/glances/core/glances_client.py b/glances/core/glances_client.py index 755b88c0..2c35570a 100644 --- a/glances/core/glances_client.py +++ b/glances/core/glances_client.py @@ -28,11 +28,6 @@ try: except ImportError: # Python 2 from xmlrpclib import Transport, ServerProxy, ProtocolError, Fault -try: - import http.client as httplib -except ImportError: - # Python 2 - import httplib # Import Glances libs from glances.core.glances_globals import version @@ -58,8 +53,8 @@ class GlancesClient(object): self.args = args self.config = config - # Client mode: - self.set_mode() + # Default client mode + self._client_mode = 'glances' # Return to browser or exit self.return_to_browser = return_to_browser @@ -89,22 +84,19 @@ class GlancesClient(object): else: logger.error(msg) - def set_mode(self, mode='glances'): + @property + def client_mode(self): + """Get the client mode.""" + return self._client_mode + + @client_mode.setter + def client_mode(self, mode): """Set the client mode. - 'glances' = Glances server (default) - 'snmp' = SNMP (fallback) """ - self.mode = mode - return self.mode - - def get_mode(self): - """Get the client mode. - - - 'glances' = Glances server (default) - - 'snmp' = SNMP (fallback) - """ - return self.mode + self._client_mode = mode def login(self): """Logon to the server.""" @@ -112,15 +104,14 @@ class GlancesClient(object): if not self.args.snmp_force: # First of all, trying to connect to a Glances server - self.set_mode('glances') client_version = None try: client_version = self.client.init() except socket.error as err: # Fallback to SNMP - logger.error("Connection to Glances server failed (%s)" % err) - self.set_mode('snmp') - fallbackmsg = _("Trying fallback to SNMP...") + self.client_mode = 'snmp' + logger.error("Connection to Glances server failed: {0}".format(err)) + fallbackmsg = _("No Glances server found. Trying fallback to SNMP...") if not self.return_to_browser: print(fallbackmsg) else: @@ -134,22 +125,25 @@ class GlancesClient(object): self.log_and_exit(msg) return False - if self.get_mode() == 'glances' and version.split('.')[0] == client_version.split('.')[0]: - # Init stats - self.stats = GlancesStatsClient(config=self.config, args=self.args) - self.stats.set_plugins(json.loads(self.client.getAllPlugins())) - logger.debug( - "Client version: %s / Server version: %s" % (version, client_version)) - elif self.get_mode() == 'glances': - self.log_and_exit("Client and server not compatible: Client version: %s / Server version: %s" % (version, client_version)) - return False + if self.client_mode == 'glances': + # Check that both client and server are in the same major version + if version.split('.')[0] == client_version.split('.')[0]: + # Init stats + self.stats = GlancesStatsClient(config=self.config, args=self.args) + self.stats.set_plugins(json.loads(self.client.getAllPlugins())) + logger.debug("Client version: {0} / Server version: {1}".format(version, client_version)) + else: + self.log_and_exit("Client and server not compatible: \ + Client version: {0} / Server version: {1}".format(version, client_version)) + return False else: - self.set_mode('snmp') + self.client_mode = 'snmp' - if self.get_mode() == 'snmp': + # SNMP mode + if self.client_mode == 'snmp': logger.info("Trying to grab stats by SNMP...") - # Fallback to SNMP if needed + from glances.core.glances_stats import GlancesStatsClientSNMP # Init stats @@ -172,13 +166,13 @@ class GlancesClient(object): def update(self): """Update stats from Glances/SNMP server.""" - if self.get_mode() == 'glances': + if self.client_mode == 'glances': return self.update_glances() - elif self.get_mode() == 'snmp': + elif self.client_mode == 'snmp': return self.update_snmp() else: self.end() - logger.critical("Unknown server mode: {0}".format(self.get_mode())) + logger.critical("Unknown server mode: {0}".format(self.client_mode)) sys.exit(2) def update_glances(self): @@ -237,7 +231,7 @@ class GlancesClient(object): # Export stats using export modules self.stats.export(self.stats) - return self.get_mode() + return self.client_mode def end(self): """End of the client session.""" diff --git a/glances/core/glances_client_browser.py b/glances/core/glances_client_browser.py index d0725c3d..66f131d2 100644 --- a/glances/core/glances_client_browser.py +++ b/glances/core/glances_client_browser.py @@ -146,42 +146,40 @@ class GlancesClientBrowser(object): "Server list dictionnary change inside the loop (wait next update)") # Update the screen (list or Glances client) - if self.screen.get_active() is None: + if self.screen.active_server is None: # Display the Glances browser self.screen.update(self.get_servers_list()) else: # Display the Glances client for the selected server - logger.debug("Selected server: %s" % self.get_servers_list()[self.screen.get_active()]) + logger.debug("Selected server: {0}".format(self.get_servers_list()[self.screen.active_server])) # Connection can take time # Display a popup self.screen.display_popup(_("Connect to %s:%s" % (v['name'], v['port'])), duration=1) # A password is needed to access to the server's stats - if self.get_servers_list()[self.screen.get_active()]['password'] is None: + if self.get_servers_list()[self.screen.active_server]['password'] is None: from hashlib import sha256 # Display a popup to enter password clear_password = self.screen.display_popup(_("Password needed for %s: " % v['name']), is_input=True) # Hash with SHA256 - encoded_password = sha256(clear_password).hexdigest() + encoded_password = sha256(clear_password.encode('utf-8')).hexdigest() # Store the password for the selected server self.set_in_selected('password', encoded_password) # Display the Glance client on the selected server - logger.info("Connect Glances client to the %s server" % - self.get_servers_list()[self.screen.get_active()]['key']) + logger.info("Connect Glances client to the {0} server".format( + self.get_servers_list()[self.screen.active_server]['key'])) # Init the client args_server = self.args # Overwrite connection setting - args_server.client = self.get_servers_list()[self.screen.get_active()]['ip'] - args_server.port = self.get_servers_list()[self.screen.get_active()]['port'] - args_server.username = self.get_servers_list()[self.screen.get_active()]['username'] - args_server.password = self.get_servers_list()[self.screen.get_active()]['password'] - client = GlancesClient(config=self.config, - args=args_server, - return_to_browser=True) + args_server.client = self.get_servers_list()[self.screen.active_server]['ip'] + args_server.port = self.get_servers_list()[self.screen.active_server]['port'] + args_server.username = self.get_servers_list()[self.screen.active_server]['username'] + args_server.password = self.get_servers_list()[self.screen.active_server]['password'] + client = GlancesClient(config=self.config, args=args_server, return_to_browser=True) # Test if client and server are in the same major version if not client.login(): @@ -195,8 +193,8 @@ class GlancesClientBrowser(object): connection_type = client.serve_forever() try: - logger.debug("Disconnect Glances client from the %s server" % - self.get_servers_list()[self.screen.get_active()]['key']) + logger.debug("Disconnect Glances client from the {0} server".format( + self.get_servers_list()[self.screen.active_server]['key'])) except IndexError: # Server did not exist anymore pass @@ -208,19 +206,17 @@ class GlancesClientBrowser(object): self.set_in_selected('status', 'ONLINE') # Return to the browser (no server selected) - self.screen.set_active(None) + self.screen.active_server = None def set_in_selected(self, key, value): - """Set the (key, value) for the selected server in the list""" + """Set the (key, value) for the selected server in the list.""" # Static list then dynamic one - if self.screen.get_active() >= len(self.static_server.get_servers_list()): - self.autodiscover_server.set_server(self.screen.get_active() - len(self.static_server.get_servers_list()), - key, - value) + if self.screen.active_server >= len(self.static_server.get_servers_list()): + self.autodiscover_server.set_server( + self.screen.active_server - len(self.static_server.get_servers_list()), + key, value) else: - self.static_server.set_server(self.screen.get_active(), - key, - value) + self.static_server.set_server(self.screen.active_server, key, value) def end(self): """End of the client browser session.""" diff --git a/glances/core/glances_logging.py b/glances/core/glances_logging.py index c7cb7e94..b5e604bb 100644 --- a/glances/core/glances_logging.py +++ b/glances/core/glances_logging.py @@ -17,10 +17,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -"""Custom logging class""" +"""Custom logging class.""" import logging -import logging.config +try: + # Python 2.6 + from logutils.dictconfig import dictConfig +except ImportError: + # Python >= 2.7 + from logging.config import dictConfig import os import tempfile @@ -72,29 +77,24 @@ LOGGING_CFG = { def tempfile_name(): - """Return the tempfile name (full path)""" + """Return the tempfile name (full path).""" ret = os.path.join(tempfile.gettempdir(), 'glances.log') if os.access(ret, os.F_OK) and not os.access(ret, os.W_OK): - print("Warning: can't write logs to file {} (permission denied)".format(ret)) + print("WARNING: Couldn't write to log file {0}: (Permission denied)".format(ret)) ret = tempfile.mkstemp(prefix='glances', suffix='.tmp', text=True) - print("Create a new log file: {}".format(ret[1])) + print("Create a new log file: {0}".format(ret[1])) return ret[1] + return ret def glances_logger(): - """Build and return the logger""" + """Build and return the logger.""" temp_path = tempfile_name() _logger = logging.getLogger() - try: - LOGGING_CFG['handlers']['file']['filename'] = temp_path - logging.config.dictConfig(LOGGING_CFG) - except AttributeError: - # dictConfig is only available for Python 2.7 or higher - # Minimal configuration for Python 2.6 - logging.basicConfig(filename=temp_path, - level=logging.DEBUG, - format='%(asctime)s -- %(levelname)s -- %(message)s') + LOGGING_CFG['handlers']['file']['filename'] = temp_path + dictConfig(LOGGING_CFG) + return _logger logger = glances_logger() diff --git a/glances/core/glances_logs.py b/glances/core/glances_logs.py index c795e17b..108613f3 100644 --- a/glances/core/glances_logs.py +++ b/glances/core/glances_logs.py @@ -87,22 +87,17 @@ class GlancesLogs(object): else: # Default sort is... process_auto_by = 'cpu_percent' - glances_processes.setautosortkey(process_auto_by) - - return process_auto_by + glances_processes.auto_sort = True + glances_processes.sort_key = process_auto_by def reset_process_sort(self): - """Reset the process_auto_by variable.""" + """Reset the process auto sort key.""" # Default sort is... - process_auto_by = 'cpu_percent' - glances_processes.setautosortkey(process_auto_by) - glances_processes.setmanualsortkey(None) - - return process_auto_by + glances_processes.auto_sort = True + glances_processes.sort_key = 'cpu_percent' def add(self, item_state, item_type, item_value, - proc_list=[], proc_desc="", - peak_time=3): + proc_list=None, proc_desc="", peak_time=6): """Add a new item to the logs list. If 'item' is a 'new one', add the new item at the beginning of the logs @@ -110,6 +105,8 @@ class GlancesLogs(object): If 'item' is not a 'new one', update the existing item. If event < peak_time the the alert is not setoff """ + proc_list = proc_list or [] + # Add or update the log item_index = self.__itemexist__(item_type) if item_index < 0: @@ -121,24 +118,23 @@ class GlancesLogs(object): # Create the new log item # Time is stored in Epoch format # Epoch -> DMYHMS = datetime.fromtimestamp(epoch) - item = [] - # START DATE - item.append(time.mktime(datetime.now().timetuple())) - item.append(-1) # END DATE - item.append(item_state) # STATE: WARNING|CRITICAL - item.append(item_type) # TYPE: CPU, LOAD, MEM... - item.append(item_value) # MAX - item.append(item_value) # AVG - item.append(item_value) # MIN - item.append(item_value) # SUM - item.append(1) # COUNT - # Process list is sorted automaticaly - # Overwrite the user choise - # topprocess = sorted(proc_list, key=lambda process: process[process_auto_by], - # reverse=True) - # item.append(topprocess[0:3]) # TOP 3 PROCESS LIST - item.append([]) # TOP 3 PROCESS LIST - item.append(proc_desc) # MONITORED PROCESSES DESC + item = [ + time.mktime(datetime.now().timetuple()), # START DATE + -1, # END DATE + item_state, # STATE: WARNING|CRITICAL + item_type, # TYPE: CPU, LOAD, MEM... + item_value, # MAX + item_value, # AVG + item_value, # MIN + item_value, # SUM + 1, # COUNT + # Process list is sorted automatically + # Overwrite the user choice + # topprocess = sorted(proc_list, key=lambda process: process[process_auto_by], + # reverse=True) + # topprocess[0:3], # TOP 3 PROCESS LIST + [], # TOP 3 PROCESS LIST + proc_desc] # MONITORED PROCESSES DESC # Add the item to the list self.logs_list.insert(0, item) diff --git a/glances/core/glances_main.py b/glances/core/glances_main.py index eb68f663..8bdc7c18 100644 --- a/glances/core/glances_main.py +++ b/glances/core/glances_main.py @@ -110,6 +110,8 @@ Start the client browser (browser mode):\n\ dest='disable_fs', help=_('disable filesystem module')) parser.add_argument('--disable-sensors', action='store_true', default=False, dest='disable_sensors', help=_('disable sensors module')) + parser.add_argument('--disable-hddtemp', action='store_true', default=False, + dest='disable_hddtemp', help=_('disable HD Temperature module')) parser.add_argument('--disable-raid', action='store_true', default=False, dest='disable_raid', help=_('disable RAID module')) parser.add_argument('--disable-docker', action='store_true', default=False, @@ -171,6 +173,8 @@ Start the client browser (browser mode):\n\ parser.add_argument('-w', '--webserver', action='store_true', default=False, dest='webserver', help=_('run Glances in web server mode (need Bootle lib)')) # Display options + parser.add_argument('-q', '--quiet', default=False, action='store_true', + dest='quiet', help=_('Do not display the Curse interface')) parser.add_argument('-f', '--process-filter', default=None, type=str, dest='process_filter', help=_('set the process filter pattern (regular expression)')) parser.add_argument('--process-short-name', action='store_true', default=False, @@ -187,7 +191,7 @@ Start the client browser (browser mode):\n\ parser.add_argument('--fs-free-space', action='store_false', default=False, dest='fs_free_space', help=_('display FS free space instead of used')) parser.add_argument('--theme-white', action='store_true', default=False, - dest='theme_white', help=_('optimize display for white background')) + dest='theme_white', help=_('optimize display colors for white background')) return parser @@ -264,6 +268,11 @@ Start the client browser (browser mode):\n\ sys.exit(2) logger.debug("History output path is set to {0}".format(args.path_history)) + # Disable HDDTemp if sensors are disabled + if args.disable_sensors: + args.disable_hddtemp = True + logger.debug("Sensors and HDDTemp are disabled") + return args def __hash_password(self, plain_password): diff --git a/glances/core/glances_monitor_list.py b/glances/core/glances_monitor_list.py index 0a64098f..04ab35c1 100644 --- a/glances/core/glances_monitor_list.py +++ b/glances/core/glances_monitor_list.py @@ -74,7 +74,6 @@ class MonitorList(object): countmax = self.config.get_raw_option(section, key + "countmax") except Exception as e: logger.error("Cannot read monitored list: {0}".format(e)) - pass else: if description is not None and regex is not None: # Build the new item diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py index a8571cb1..d5b63722 100644 --- a/glances/core/glances_processes.py +++ b/glances/core/glances_processes.py @@ -250,11 +250,11 @@ class GlancesProcesses(object): self.process_tree = None # Init stats - self.resetsort() + self.auto_sort = True + self._sort_key = 'cpu_percent' self.allprocesslist = [] self.processlist = [] - self.processcount = { - 'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0} + self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0} # Tag to enable/disable the processes stats (to reduce the Glances CPU consumption) # Default is to enable the processes stats @@ -263,13 +263,12 @@ class GlancesProcesses(object): # Extended stats for top process is enable by default self.disable_extended_tag = False - # Maximum number of processes showed in the UI interface - # None if no limit - self.max_processes = None + # Maximum number of processes showed in the UI (None if no limit) + self._max_processes = None # Process filter is a regular expression - self.process_filter = None - self.process_filter_re = None + self._process_filter = None + self._process_filter_re = None # Whether or not to hide kernel threads self.no_kernel_threads = False @@ -292,48 +291,49 @@ class GlancesProcesses(object): """Disable extended process stats.""" self.disable_extended_tag = True - def set_max_processes(self, value): - """Set the maximum number of processes showed in the UI interfaces""" - self.max_processes = value - return self.max_processes + @property + def max_processes(self): + """Get the maximum number of processes showed in the UI.""" + return self._max_processes - def get_max_processes(self): - """Get the maximum number of processes showed in the UI interfaces""" - return self.max_processes + @max_processes.setter + def max_processes(self, value): + """Set the maximum number of processes showed in the UI.""" + self._max_processes = value - def set_process_filter(self, value): - """Set the process filter""" + @property + def process_filter(self): + """Get the process filter.""" + return self._process_filter + + @process_filter.setter + def process_filter(self, value): + """Set the process filter.""" logger.info("Set process filter to {0}".format(value)) - self.process_filter = value + self._process_filter = value if value is not None: try: - self.process_filter_re = re.compile(value) - logger.debug( - "Process filter regex compilation OK: {0}".format(self.get_process_filter())) + self._process_filter_re = re.compile(value) + logger.debug("Process filter regex compilation OK: {0}".format(self.process_filter)) except Exception: - logger.error( - "Cannot compile process filter regex: {0}".format(value)) - self.process_filter_re = None + logger.error("Cannot compile process filter regex: {0}".format(value)) + self._process_filter_re = None else: - self.process_filter_re = None - return self.process_filter + self._process_filter_re = None - def get_process_filter(self): - """Get the process filter""" - return self.process_filter - - def get_process_filter_re(self): - """Get the process regular expression compiled""" - return self.process_filter_re + @property + def process_filter_re(self): + """Get the process regular expression compiled.""" + return self._process_filter_re def is_filtered(self, value): """Return True if the value should be filtered""" - if self.get_process_filter() is None: + if self.process_filter is None: # No filter => Not filtered return False else: - # logger.debug(self.get_process_filter() + " <> " + value + " => " + str(self.get_process_filter_re().match(value) is None)) - return self.get_process_filter_re().match(value) is None + # logger.debug(self.process_filter + " <> " + value + " => " + str(self.process_filter_re.match(value) is None)) + return self.process_filter_re.match(value) is None def disable_kernel_threads(self): """ Ignore kernel threads in process list. """ @@ -541,8 +541,7 @@ class GlancesProcesses(object): """ # Reset the stats self.processlist = [] - self.processcount = { - 'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0} + self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0} # Do not process if disable tag is set if self.disable_tag: @@ -558,11 +557,11 @@ class GlancesProcesses(object): if self.no_kernel_threads and not is_windows and is_kernel_thread(proc): continue - # If self.get_max_processes() is None: Only retreive mandatory stats + # If self.max_processes is None: Only retreive mandatory stats # Else: retreive mandatory and standard stats s = self.__get_process_stats(proc, mandatory_stats=True, - standard_stats=self.get_max_processes() is None) + standard_stats=self.max_processes is None) # Continue to the next process if it has to be filtered if s is None or (self.is_filtered(s['cmdline']) and self.is_filtered(s['name'])): continue @@ -571,8 +570,10 @@ class GlancesProcesses(object): # ignore the 'idle' process on Windows and *BSD # ignore the 'kernel_task' process on OS X # waiting for upstream patch from psutil - if is_bsd and processdict[proc]['name'] == 'idle' or is_windows and processdict[proc]['name'] == 'System Idle Process' or is_mac and processdict[proc]['name'] == 'kernel_task': - continue + if (is_bsd and processdict[proc]['name'] == 'idle' or + is_windows and processdict[proc]['name'] == 'System Idle Process' or + is_mac and processdict[proc]['name'] == 'kernel_task'): + continue # Update processcount (global statistics) try: self.processcount[str(proc.status())] += 1 @@ -594,12 +595,12 @@ class GlancesProcesses(object): if self._enable_tree: self.process_tree = ProcessTreeNode.build_tree(processdict, - self.getsortkey(), + self.sort_key, self.no_kernel_threads) for i, node in enumerate(self.process_tree): - # Only retreive stats for visible processes (get_max_processes) - if self.get_max_processes() is not None and i >= self.get_max_processes(): + # Only retreive stats for visible processes (max_processes) + if self.max_processes is not None and i >= self.max_processes: break # add standard stats @@ -615,22 +616,21 @@ class GlancesProcesses(object): else: # Process optimization - # Only retreive stats for visible processes (get_max_processes) - if self.get_max_processes() is not None: + # Only retreive stats for visible processes (max_processes) + if self.max_processes is not None: # Sort the internal dict and cut the top N (Return a list of tuple) # tuple=key (proc), dict (returned by __get_process_stats) try: processiter = sorted( - processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True) + processdict.items(), key=lambda x: x[1][self.sort_key], reverse=True) except (KeyError, TypeError) as e: - logger.error( - "Cannot sort process list by %s (%s)" % (self.getsortkey(), e)) + logger.error("Cannot sort process list by {0}: {1}".format(self.sort_key, e)) logger.error("%s" % str(processdict.items()[0])) # Fallback to all process (issue #423) processloop = processdict.items() first = False else: - processloop = processiter[0:self.get_max_processes()] + processloop = processiter[0:self.max_processes] first = True else: # Get all processes stats @@ -640,7 +640,7 @@ class GlancesProcesses(object): for i in processloop: # Already existing mandatory stats procstat = i[1] - if self.get_max_processes() is not None: + if self.max_processes is not None: # Update with standard stats # and extended stats but only for TOP (first) process s = self.__get_process_stats(i[0], @@ -687,37 +687,17 @@ class GlancesProcesses(object): """Get the process tree.""" return self.process_tree - def getsortkey(self): - """Get the current sort key""" - if self.getmanualsortkey() is not None: - return self.getmanualsortkey() - else: - return self.getautosortkey() + @property + def sort_key(self): + """Get the current sort key.""" + return self._sort_key - def getmanualsortkey(self): - """Get the current sort key for manual sort.""" - return self.processmanualsort - - def getautosortkey(self): - """Get the current sort key for automatic sort.""" - return self.processautosort - - def setmanualsortkey(self, sortedby): - """Set the current sort key for manual sort.""" - self.processmanualsort = sortedby - if self._enable_tree and (self.process_tree is not None): - self.process_tree.set_sorting(sortedby, sortedby != "name") - return self.processmanualsort - - def setautosortkey(self, sortedby): - """Set the current sort key for automatic sort.""" - self.processautosort = sortedby - return self.processautosort - - def resetsort(self): - """Set the default sort: Auto""" - self.setmanualsortkey(None) - self.setautosortkey('cpu_percent') + @sort_key.setter + def sort_key(self, key): + """Set the current sort key.""" + self._sort_key = key + if not self.auto_sort and self._enable_tree and self.process_tree is not None: + self.process_tree.set_sorting(key, key != "name") def getsortlist(self, sortedby=None): """Get the sorted processlist.""" diff --git a/glances/core/glances_server.py b/glances/core/glances_server.py index 6cf8176a..420b2221 100644 --- a/glances/core/glances_server.py +++ b/glances/core/glances_server.py @@ -100,7 +100,7 @@ class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler): self.send_error(401, 'Authentication failed') return False - def log_message(self, format, *args): + def log_message(self, log_format, *args): # No message displayed on the server side pass diff --git a/glances/core/glances_standalone.py b/glances/core/glances_standalone.py index d2b277cf..5cb149d0 100644 --- a/glances/core/glances_standalone.py +++ b/glances/core/glances_standalone.py @@ -32,12 +32,12 @@ class GlancesStandalone(object): """This class creates and manages the Glances standalone session.""" def __init__(self, config=None, args=None): + # Quiet mode + self._quiet = args.quiet + # Init stats self.stats = GlancesStats(config=config, args=args) - # Default number of processes to displayed is set to 50 - glances_processes.set_max_processes(50) - # If process extended stats is disabled by user if not args.enable_process_extended: logger.debug("Extended stats for top process are disabled") @@ -48,7 +48,7 @@ class GlancesStandalone(object): # Manage optionnal process filter if args.process_filter is not None: - glances_processes.set_process_filter(args.process_filter) + glances_processes.process_filter = args.process_filter if (not is_windows) and args.no_kernel_threads: # Ignore kernel threads in process list @@ -61,8 +61,20 @@ class GlancesStandalone(object): # Initial system informations update self.stats.update() - # Init screen - self.screen = GlancesCursesStandalone(args=args) + if self.quiet: + logger.info("Quiet mode is ON: Nothing will be displayed") + # In quiet mode, nothing is displayed + glances_processes.max_processes = 0 + else: + # Default number of processes to displayed is set to 50 + glances_processes.max_processes = 50 + + # Init screen + self.screen = GlancesCursesStandalone(args=args) + + @property + def quiet(self): + return self._quiet def serve_forever(self): """Main loop for the CLI.""" @@ -71,14 +83,16 @@ class GlancesStandalone(object): self.stats.update() # Update the screen - self.screen.update(self.stats) + if not self.quiet: + self.screen.update(self.stats) # Export stats using export modules self.stats.export(self.stats) def end(self): """End of the standalone CLI.""" - self.screen.end() + if not self.quiet: + self.screen.end() # Exit from export modules self.stats.end() diff --git a/glances/core/glances_stats.py b/glances/core/glances_stats.py index 8b92334f..100d7cfd 100644 --- a/glances/core/glances_stats.py +++ b/glances/core/glances_stats.py @@ -155,10 +155,12 @@ class GlancesStats(object): # logger.debug("Update %s stats" % p) self._plugins[p].update() - def export(self, input_stats={}): + def export(self, input_stats=None): """Export all the stats. Each export module is ran in a dedicated thread.""" # threads = [] + input_stats = input_stats or {} + for e in self._exports: logger.debug("Export stats using the %s module" % e) thread = threading.Thread(target=self._exports[e].update, @@ -181,13 +183,13 @@ class GlancesStats(object): def getAllLimits(self): """Return the plugins limits list.""" - return [self._plugins[p].get_limits() for p in self._plugins] + return [self._plugins[p].limits for p in self._plugins] def getAllLimitsAsDict(self): """Return all the stats limits (dict)""" ret = {} for p in self._plugins: - ret[p] = self._plugins[p].get_limits() + ret[p] = self._plugins[p].limits return ret def getAllViews(self): @@ -203,7 +205,7 @@ class GlancesStats(object): def get_plugin_list(self): """Return the plugin list.""" - self._plugins + return self._plugins def get_plugin(self, plugin_name): """Return the plugin name.""" @@ -231,8 +233,10 @@ class GlancesStatsServer(GlancesStats): # all_stats is a dict of dicts filled by the server self.all_stats = collections.defaultdict(dict) - def update(self, input_stats={}): + def update(self, input_stats=None): """Update the stats.""" + input_stats = input_stats or {} + # Force update of all the stats GlancesStats.update(self) @@ -383,7 +387,8 @@ class GlancesStatsClientSNMP(GlancesStats): # For each plugins, call the update method for p in self._plugins: # Set the input method to SNMP - self._plugins[p].set_input('snmp', self.system_name) + self._plugins[p].input_method = 'snmp' + self._plugins[p].short_system_name = self.system_name try: self._plugins[p].update() except Exception as e: diff --git a/glances/exports/glances_export.py b/glances/exports/glances_export.py index a8681a64..5f32444a 100644 --- a/glances/exports/glances_export.py +++ b/glances/exports/glances_export.py @@ -58,8 +58,10 @@ class GlancesExport(object): def update(self, stats): """Update stats to a server. - The method buil two list: names and values - and call the export method to export the stats""" + + The method builds two lists: names and values + and calls the export method to export the stats. + """ if not self.export_enable: return False @@ -67,20 +69,17 @@ class GlancesExport(object): all_stats = stats.getAll() plugins = stats.getAllPlugins() - # Loop over available plugin - i = 0 - for plugin in plugins: + # Loop over available plugins + for i, plugin in enumerate(plugins): if plugin in self.plugins_to_export(): if type(all_stats[i]) is list: for item in all_stats[i]: - export_names = map( - lambda x: item[item['key']] + '.' + x, item.keys()) - export_values = item.values() + export_names = list(map(lambda x: item[item['key']] + '.' + x, item.keys())) + export_values = list(item.values()) self.export(plugin, export_names, export_values) elif type(all_stats[i]) is dict: - export_names = all_stats[i].keys() - export_values = all_stats[i].values() + export_names = list(all_stats[i].keys()) + export_values = list(all_stats[i].values()) self.export(plugin, export_names, export_values) - i += 1 return True diff --git a/glances/exports/glances_history.py b/glances/exports/glances_history.py index 674bdb5c..411a9856 100644 --- a/glances/exports/glances_history.py +++ b/glances/exports/glances_history.py @@ -176,8 +176,7 @@ class GlancesHistory(object): fig.set_size_inches(20, 10) plt.legend(handles, labels, loc=1, prop={'size': 9}) plt.xlabel('Date') - plt.savefig( - os.path.join(self.output_folder, 'glances_%s.png' % (p)), dpi=72) + plt.savefig(os.path.join(self.output_folder, 'glances_%s.png' % p), dpi=72) index_all += 1 plt.close() diff --git a/glances/exports/glances_influxdb.py b/glances/exports/glances_influxdb.py index 60b9c1ee..416872ec 100644 --- a/glances/exports/glances_influxdb.py +++ b/glances/exports/glances_influxdb.py @@ -20,14 +20,19 @@ """InfluxDB interface class.""" # Import sys libs -from influxdb import InfluxDBClient, client import sys +try: + from configparser import NoOptionError, NoSectionError +except ImportError: # Python 2 + from ConfigParser import NoOptionError, NoSectionError # Import Glances lib from glances.core.glances_logging import logger -from ConfigParser import NoSectionError, NoOptionError from glances.exports.glances_export import GlancesExport +from influxdb import InfluxDBClient, client +from influxdb.influxdb08 import InfluxDBClient as InfluxDBClient_Legacy + class Export(GlancesExport): @@ -38,11 +43,12 @@ class Export(GlancesExport): GlancesExport.__init__(self, config=config, args=args) # Load the InfluxDB configuration file - self.influxdb_host = None - self.influxdb_port = None - self.influxdb_user = None - self.influxdb_password = None - self.influxdb_db = None + self.host = None + self.port = None + self.user = None + self.password = None + self.db = None + self.prefix = None self.export_enable = self.load_conf() if not self.export_enable: sys.exit(2) @@ -55,11 +61,11 @@ class Export(GlancesExport): if self.config is None: return False try: - self.influxdb_host = self.config.get_raw_option(section, "host") - self.influxdb_port = self.config.get_raw_option(section, "port") - self.influxdb_user = self.config.get_raw_option(section, "user") - self.influxdb_password = self.config.get_raw_option(section, "password") - self.influxdb_db = self.config.get_raw_option(section, "db") + self.host = self.config.get_raw_option(section, "host") + self.port = self.config.get_raw_option(section, "port") + self.user = self.config.get_raw_option(section, "user") + self.password = self.config.get_raw_option(section, "password") + self.db = self.config.get_raw_option(section, "db") except NoSectionError: logger.critical("No InfluxDB configuration found") return False @@ -68,39 +74,61 @@ class Export(GlancesExport): return False else: logger.debug("Load InfluxDB from the Glances configuration file") + # Prefix is optional + try: + self.prefix = self.config.get_raw_option(section, "prefix") + except NoOptionError as e: + pass return True def init(self): """Init the connection to the InfluxDB server""" if not self.export_enable: return None - db = InfluxDBClient(self.influxdb_host, - self.influxdb_port, - self.influxdb_user, - self.influxdb_password, - self.influxdb_db) - try: - get_all_db = db.get_database_list()[0].values() - except client.InfluxDBClientError as e: - logger.critical("Can not connect to InfluxDB database '%s' (%s)" % (self.influxdb_db, e)) - sys.exit(2) - if self.influxdb_db in get_all_db: + try: + db = InfluxDBClient(host=self.host, + port=self.port, + username=self.user, + password=self.password, + database=self.db) + get_all_db = [i['name'] for i in db.get_list_database()] + except client.InfluxDBClientError as e: + try: + # https://github.com/influxdb/influxdb-python/issues/138 + logger.info("Trying fallback to InfluxDB v0.8") + db = InfluxDBClient_Legacy(host=self.host, + port=self.port, + username=self.user, + password=self.password, + database=self.db) + get_all_db = [i['name'] for i in db.get_list_database()] + except: + logger.critical("Can not connect to InfluxDB database '%s' (%s)" % (self.db, e)) + sys.exit(2) + + if self.db in get_all_db: logger.info( "Stats will be exported to InfluxDB server: {0}".format(db._baseurl)) else: - logger.critical("InfluxDB database '%s' did not exist. Please create it" % self.influxdb_db) + logger.critical("InfluxDB database '%s' did not exist. Please create it" % self.db) sys.exit(2) return db def export(self, name, columns, points): """Write the points to the InfluxDB server""" + # Manage prefix + if self.prefix is not None: + name = self.prefix + '.' + name + # logger.info(self.prefix) + # Create DB input data = [ { "name": name, "columns": columns, "points": [points] }] + # Write input to the InfluxDB database try: self.client.write_points(data) except Exception as e: diff --git a/glances/exports/glances_statsd.py b/glances/exports/glances_statsd.py index 982a9bfb..7a29548e 100644 --- a/glances/exports/glances_statsd.py +++ b/glances/exports/glances_statsd.py @@ -20,15 +20,19 @@ """Statsd interface class.""" # Import sys libs -from statsd import StatsClient -from numbers import Number import sys +from numbers import Number +try: + from configparser import NoOptionError, NoSectionError +except ImportError: # Python 2 + from ConfigParser import NoOptionError, NoSectionError # Import Glances lib from glances.core.glances_logging import logger -from ConfigParser import NoSectionError, NoOptionError from glances.exports.glances_export import GlancesExport +from statsd import StatsClient + class Export(GlancesExport): diff --git a/glances/outputs/glances_bars.py b/glances/outputs/glances_bars.py index c83b7692..f149fef3 100644 --- a/glances/outputs/glances_bars.py +++ b/glances/outputs/glances_bars.py @@ -33,7 +33,7 @@ class Bar(object): import time b = Bar(10) for p in range(0, 100): - b.set_percent(p) + b.percent = p print("\r%s" % b), time.sleep(0.1) sys.stdout.flush() @@ -43,7 +43,8 @@ class Bar(object): def __init__(self, size, pre_char='[', post_char=']', - empty_char='_'): + empty_char='_', + with_text=True): # Bar size self.__size = size # Bar current percent @@ -52,29 +53,38 @@ class Bar(object): self.__pre_char = pre_char self.__post_char = post_char self.__empty_char = empty_char + self.__with_text = with_text - def get_size(self): - return self.__size + @property + def size(self, with_decoration=False): + # Return the bar size, with or without decoration + if with_decoration: + return self.__size + if self.__with_text: + return self.__size - 6 - def set_size(self, size): - self.__size = size - return self.__size + # @size.setter + # def size(self, value): + # self.__size = value - def get_percent(self): + @property + def percent(self): return self.__percent - def set_percent(self, percent): - assert percent >= 0 - assert percent <= 100 - self.__percent = percent - return self.__percent + @percent.setter + def percent(self, value): + assert value >= 0 + assert value <= 100 + self.__percent = value def __str__(self): - """Return the bars""" - frac, whole = modf(self.get_size() * self.get_percent() / 100.0) + """Return the bars.""" + frac, whole = modf(self.size * self.percent / 100.0) ret = curses_bars[8] * int(whole) if frac > 0: ret += curses_bars[int(frac * 8)] whole += 1 - ret += self.__empty_char * int(self.get_size() - whole) + ret += self.__empty_char * int(self.size - whole) + if self.__with_text: + ret = '{0}{1:>5}%'.format(ret, self.percent) return self.__pre_char + ret + self.__post_char diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index b70ba8cd..3fd66f8c 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -279,7 +279,7 @@ class GlancesBottle(object): try: # Get the JSON value of the stat limits - ret = self.stats.get_plugin(plugin).get_limits() + ret = self.stats.get_plugin(plugin).limits except Exception as e: abort(404, "Cannot get limits for plugin %s (%s)" % (plugin, str(e))) return ret diff --git a/glances/outputs/glances_colorconsole.py b/glances/outputs/glances_colorconsole.py index 49c55037..3cd674e1 100644 --- a/glances/outputs/glances_colorconsole.py +++ b/glances/outputs/glances_colorconsole.py @@ -80,10 +80,10 @@ class Screen(object): def subwin(self, x, y): return self - def keypad(self, id): + def keypad(self, screen_id): return None - def nodelay(self, id): + def nodelay(self, screen_id): return None def getch(self): @@ -170,8 +170,8 @@ class WCurseLight(object): def napms(self, t): time.sleep(t / 1000 if t > 1000 else 1) - def init_pair(self, id, fg, bk): - self.colors[id] = [max(fg, 0), max(bk, 0)] + def init_pair(self, color_id, fg, bk): + self.colors[color_id] = [max(fg, 0), max(bk, 0)] - def color_pair(self, id): - return id + def color_pair(self, color_id): + return color_id diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index cc7f10f6..6c7c03fd 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -177,9 +177,6 @@ class _GlancesCurses(object): # Init refresh time self.__refresh_time = args.time - # Init process sort method - self.args.process_sorted_by = 'auto' - # Init edit filter tag self.edit_filter = False @@ -257,17 +254,17 @@ class _GlancesCurses(object): # '/' > Switch between short/long name for processes self.args.process_short_name = not self.args.process_short_name elif self.pressedkey == ord('a'): - # 'a' > Sort processes automatically - self.args.process_sorted_by = 'auto' - glances_processes.resetsort() + # 'a' > Sort processes automatically and reset to 'cpu_percent' + glances_processes.auto_sort = True + glances_processes.sort_key = 'cpu_percent' elif self.pressedkey == ord('b'): # 'b' > Switch between bit/s and Byte/s for network IO # self.net_byteps_tag = not self.net_byteps_tag self.args.byte = not self.args.byte elif self.pressedkey == ord('c'): # 'c' > Sort processes by CPU usage - self.args.process_sorted_by = 'cpu_percent' - glances_processes.setmanualsortkey(self.args.process_sorted_by) + glances_processes.auto_sort = False + glances_processes.sort_key = 'cpu_percent' elif self.pressedkey == ord('d'): # 'd' > Show/hide disk I/O stats self.args.disable_diskio = not self.args.disable_diskio @@ -295,8 +292,8 @@ class _GlancesCurses(object): self.args.help_tag = not self.args.help_tag elif self.pressedkey == ord('i'): # 'i' > Sort processes by IO rate (not available on OS X) - self.args.process_sorted_by = 'io_counters' - glances_processes.setmanualsortkey(self.args.process_sorted_by) + glances_processes.auto_sort = False + glances_processes.sort_key = 'io_counters' elif self.pressedkey == ord('I'): # 'I' > Show/hide IP module self.args.disable_ip = not self.args.disable_ip @@ -305,15 +302,15 @@ class _GlancesCurses(object): self.args.disable_log = not self.args.disable_log elif self.pressedkey == ord('m'): # 'm' > Sort processes by MEM usage - self.args.process_sorted_by = 'memory_percent' - glances_processes.setmanualsortkey(self.args.process_sorted_by) + glances_processes.auto_sort = False + glances_processes.sort_key = 'memory_percent' elif self.pressedkey == ord('n'): # 'n' > Show/hide network stats self.args.disable_network = not self.args.disable_network elif self.pressedkey == ord('p'): # 'p' > Sort processes by name - self.args.process_sorted_by = 'name' - glances_processes.setmanualsortkey(self.args.process_sorted_by) + glances_processes.auto_sort = False + glances_processes.sort_key = 'name' elif self.pressedkey == ord('r'): # 'r' > Reset history self.reset_history_tag = not self.reset_history_tag @@ -325,8 +322,8 @@ class _GlancesCurses(object): self.args.disable_sensors = not self.args.disable_sensors elif self.pressedkey == ord('t'): # 't' > Sort processes by TIME usage - self.args.process_sorted_by = 'cpu_times' - glances_processes.setmanualsortkey(self.args.process_sorted_by) + glances_processes.auto_sort = False + glances_processes.sort_key = 'cpu_times' elif self.pressedkey == ord('T'): # 'T' > View network traffic as sum Rx+Tx self.args.network_sum = not self.args.network_sum @@ -389,7 +386,7 @@ class _GlancesCurses(object): """New column in the curses interface""" self.column = self.next_column - def display(self, stats, cs_status="None"): + def display(self, stats, cs_status=None): """Display stats on the screen. stats: Stats database to display @@ -464,11 +461,10 @@ class _GlancesCurses(object): max_processes_displayed -= 4 if max_processes_displayed < 0: max_processes_displayed = 0 - if glances_processes.get_max_processes() is None or \ - glances_processes.get_max_processes() != max_processes_displayed: - logger.debug("Set number of displayed processes to %s" % - max_processes_displayed) - glances_processes.set_max_processes(max_processes_displayed) + if (glances_processes.max_processes is None or + glances_processes.max_processes != max_processes_displayed): + logger.debug("Set number of displayed processes to {0}".format(max_processes_displayed)) + glances_processes.max_processes = max_processes_displayed stats_processlist = stats.get_plugin( 'processlist').get_stats_display(args=self.args) @@ -600,7 +596,7 @@ class _GlancesCurses(object): self.display_plugin(stats_docker) self.new_line() self.display_plugin(stats_processcount) - if glances_processes.get_process_filter() is None and cs_status == 'None': + if glances_processes.process_filter is None and cs_status is None: # Do not display stats monitor list if a filter exist self.new_line() self.display_plugin(stats_monitor) @@ -635,12 +631,12 @@ class _GlancesCurses(object): self.reset_history_tag = False # Display edit filter popup - # Only in standalone mode (cs_status == 'None') - if self.edit_filter and cs_status == 'None': + # Only in standalone mode (cs_status is None) + if self.edit_filter and cs_status is None: new_filter = self.display_popup(_("Process filter pattern: "), is_input=True, - input_value=glances_processes.get_process_filter()) - glances_processes.set_process_filter(new_filter) + input_value=glances_processes.process_filter) + glances_processes.process_filter = new_filter elif self.edit_filter and cs_status != 'None': self.display_popup( _("Process filter only available in standalone mode")) @@ -765,7 +761,7 @@ class _GlancesCurses(object): # New line if m['msg'].startswith('\n'): # Go to the next line - y = y + 1 + y += 1 # Return to the first column x = display_x continue @@ -802,7 +798,7 @@ class _GlancesCurses(object): # Python 3: strings are strings and bytes are bytes, all is # good offset = len(m['msg']) - x = x + offset + x += offset if x > x_max: x_max = x @@ -815,7 +811,7 @@ class _GlancesCurses(object): """Erase the content of the screen.""" self.term_window.erase() - def flush(self, stats, cs_status="None"): + def flush(self, stats, cs_status=None): """Clear and update the screen. stats: Stats database to display @@ -827,7 +823,7 @@ class _GlancesCurses(object): self.erase() self.display(stats, cs_status=cs_status) - def update(self, stats, cs_status="None", return_to_browser=False): + def update(self, stats, cs_status=None, return_to_browser=False): """Update the screen. Wait for __refresh_time sec / catch key every 100 ms. @@ -936,32 +932,30 @@ class GlancesCursesBrowser(_GlancesCurses): self.__refresh_time = args.time # Init the cursor position for the client browser - self.cursor_init() + self.cursor_position = 0 # Active Glances server number - self.set_active() + self._active_server = None - def set_active(self, index=None): - """Set the active server or None if no server selected""" - self.active_server = index - return self.active_server + @property + def active_server(self): + """Return the active server or None if it's the browser list.""" + return self._active_server - def get_active(self): - """Return the active server (the one display in front) or None if it is the browser list""" - return self.active_server + @active_server.setter + def active_server(self, index): + """Set the active server or None if no server selected.""" + self._active_server = index - def cursor_init(self): - """Init the cursor position to the top of the list""" - return self.cursor_set(0) - - def cursor_set(self, pos): - """Set the cursor position and return it""" - self.cursor_position = pos + @property + def cursor(self): + """Get the cursor position.""" return self.cursor_position - def cursor_get(self): - """Return the cursor position""" - return self.cursor_position + @cursor.setter + def cursor(self, position): + """Set the cursor position.""" + self.cursor_position = position def cursor_up(self, servers_list): """Set the cursor to position N-1 in the list""" @@ -969,7 +963,6 @@ class GlancesCursesBrowser(_GlancesCurses): self.cursor_position -= 1 else: self.cursor_position = len(servers_list) - 1 - return self.cursor_position def cursor_down(self, servers_list): """Set the cursor to position N-1 in the list""" @@ -977,7 +970,6 @@ class GlancesCursesBrowser(_GlancesCurses): self.cursor_position += 1 else: self.cursor_position = 0 - return self.cursor_position def __catch_key(self, servers_list): # Catch the browser pressed key @@ -991,11 +983,10 @@ class GlancesCursesBrowser(_GlancesCurses): sys.exit(0) elif self.pressedkey == 10: # 'ENTER' > Run Glances on the selected server - logger.debug("Server number %s selected" % (self.cursor_get() + 1)) - self.set_active(self.cursor_get()) + logger.debug("Server number {0} selected".format(self.cursor + 1)) + self.active_server = self.cursor elif self.pressedkey == 259: # 'UP' > Up in the server list - logger self.cursor_up(servers_list) elif self.pressedkey == 258: # 'DOWN' > Down in the server list @@ -1029,7 +1020,7 @@ class GlancesCursesBrowser(_GlancesCurses): # Wait 100ms... curses.napms(100) - return self.get_active() + return self.active_server def flush(self, servers_list): """Update the servers' list screen. @@ -1110,9 +1101,9 @@ class GlancesCursesBrowser(_GlancesCurses): # If a servers has been deleted from the list... # ... and if the cursor is in the latest position - if self.cursor_get() > len(servers_list) - 1: + if self.cursor > len(servers_list) - 1: # Set the cursor position to the latest item - self.cursor_set(len(servers_list) - 1) + self.cursor = len(servers_list) - 1 # Display table line = 0 @@ -1138,25 +1129,18 @@ class GlancesCursesBrowser(_GlancesCurses): xc = x # Is the line selected ? - if line == self.cursor_get(): + if line == self.cursor: # Display cursor - self.term_window.addnstr(y, xc, - ">", - screen_x - xc, - self.colors_list['BOLD']) - - # Display alias instead of name - server_stat + self.term_window.addnstr( + y, xc, ">", screen_x - xc, self.colors_list['BOLD']) # Display the line xc += 2 for c in column_def: if xc < screen_x and y < screen_y and c[1] is not None: # Display server stats - self.term_window.addnstr(y, xc, - "%s" % server_stat[c[0]], - c[2], - self.colors_list[v['status']]) + self.term_window.addnstr( + y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']]) xc += c[2] + self.space_between_column cpt += 1 # Next line, next server... diff --git a/glances/plugins/glances_alert.py b/glances/plugins/glances_alert.py index 78c6c45a..34b5aec0 100644 --- a/glances/plugins/glances_alert.py +++ b/glances/plugins/glances_alert.py @@ -42,7 +42,7 @@ class Plugin(GlancesPlugin): self.display_curse = True # Set the message position - self.set_align('bottom') + self.align = 'bottom' # Init the stats self.reset() diff --git a/glances/plugins/glances_batpercent.py b/glances/plugins/glances_batpercent.py index ba6ac7da..53f5ea58 100644 --- a/glances/plugins/glances_batpercent.py +++ b/glances/plugins/glances_batpercent.py @@ -28,7 +28,6 @@ try: import batinfo except ImportError: logger.debug("Batinfo library not found. Glances cannot grab battery info.") - pass class Plugin(GlancesPlugin): @@ -62,12 +61,12 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats self.glancesgrabbat.update() self.stats = self.glancesgrabbat.get() - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # Not avalaible pass @@ -94,7 +93,7 @@ class GlancesGrabBat(object): """Update the stats.""" if self.initok: self.bat.update() - self.bat_list = [{'label': _("Battery"), 'value': self.getcapacitypercent(), 'unit': '%'}] + self.bat_list = [{'label': _("Battery"), 'value': self.battery_percent, 'unit': '%'}] else: self.bat_list = [] @@ -102,7 +101,8 @@ class GlancesGrabBat(object): """Get the stats.""" return self.bat_list - def getcapacitypercent(self): + @property + def battery_percent(self): """Get batteries capacity percent.""" if not self.initok or not self.bat.stat: return [] @@ -112,7 +112,7 @@ class GlancesGrabBat(object): bsum = 0 for b in self.bat.stat: try: - bsum = bsum + int(b.capacity) + bsum += int(b.capacity) except ValueError: return [] diff --git a/glances/plugins/glances_core.py b/glances/plugins/glances_core.py index 20127c8b..d90575ef 100644 --- a/glances/plugins/glances_core.py +++ b/glances/plugins/glances_core.py @@ -56,7 +56,7 @@ class Plugin(GlancesPlugin): # Reset the stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # The PSUtil 2.0 include psutil.cpu_count() and psutil.cpu_count(logical=False) @@ -70,7 +70,7 @@ class Plugin(GlancesPlugin): except NameError: self.reset() - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # http://stackoverflow.com/questions/5662467/how-to-find-out-the-number-of-cpus-using-snmp pass diff --git a/glances/plugins/glances_cpu.py b/glances/plugins/glances_cpu.py index da5294cd..3d961dc4 100644 --- a/glances/plugins/glances_cpu.py +++ b/glances/plugins/glances_cpu.py @@ -78,7 +78,7 @@ class Plugin(GlancesPlugin): # Grab CPU stats using psutil's cpu_percent and cpu_times_percent # methods - if self.get_input() == 'local': + if self.input_method == 'local': # Get all possible values for CPU stats: user, system, idle, # nice (UNIX), iowait (Linux), irq (Linux, FreeBSD), steal (Linux 2.6.11+) # The following stats are returned by the API but not displayed in the UI: @@ -89,14 +89,14 @@ class Plugin(GlancesPlugin): 'irq', 'softirq', 'steal', 'guest', 'guest_nice']: if hasattr(cpu_times_percent, stat): self.stats[stat] = getattr(cpu_times_percent, stat) - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP - if self.get_short_system_name() in ('windows', 'esxi'): + if self.short_system_name in ('windows', 'esxi'): # Windows or VMWare ESXi # You can find the CPU utilization of windows system by querying the oid # Give also the number of core (number of element in the table) try: - cpu_stats = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + cpu_stats = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True) except KeyError: self.reset() @@ -117,10 +117,10 @@ class Plugin(GlancesPlugin): else: # Default behavor try: - self.stats = self.set_stats_snmp( - snmp_oid=snmp_oid[self.get_short_system_name()]) + self.stats = self.get_stats_snmp( + snmp_oid=snmp_oid[self.short_system_name]) except KeyError: - self.stats = self.set_stats_snmp( + self.stats = self.get_stats_snmp( snmp_oid=snmp_oid['default']) if self.stats['idle'] == '': @@ -150,9 +150,8 @@ class Plugin(GlancesPlugin): for key in ['user', 'system', 'iowait']: if key in self.stats: self.views[key]['decoration'] = self.get_alert_log(self.stats[key], header=key) - self.views['total']['decoration'] = self.get_alert_log(self.stats['total'], header="system") # Alert only - for key in ['steal']: + for key in ['steal', 'total']: if key in self.stats: self.views[key]['decoration'] = self.get_alert(self.stats[key], header=key) # Optional @@ -166,7 +165,7 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist... - if self.stats == {}: + if not self.stats: return ret # Build the string message diff --git a/glances/plugins/glances_diskio.py b/glances/plugins/glances_diskio.py index e0ee3cb5..882315c6 100644 --- a/glances/plugins/glances_diskio.py +++ b/glances/plugins/glances_diskio.py @@ -66,7 +66,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Grab the stat using the PsUtil disk_io_counters method # read_count: number of reads @@ -96,16 +96,15 @@ class Plugin(GlancesPlugin): diskio_new = diskiocounters for disk in diskio_new: try: - # Try necessary to manage dynamic disk creation/del - diskstat = {} - diskstat['time_since_update'] = time_since_update - diskstat['disk_name'] = disk - diskstat['read_bytes'] = ( - diskio_new[disk].read_bytes - - self.diskio_old[disk].read_bytes) - diskstat['write_bytes'] = ( - diskio_new[disk].write_bytes - - self.diskio_old[disk].write_bytes) + read_bytes = (diskio_new[disk].read_bytes - + self.diskio_old[disk].read_bytes) + write_bytes = (diskio_new[disk].write_bytes - + self.diskio_old[disk].write_bytes) + diskstat = { + 'time_since_update': time_since_update, + 'disk_name': disk, + 'read_bytes': read_bytes, + 'write_bytes': write_bytes} except KeyError: continue else: @@ -114,7 +113,7 @@ class Plugin(GlancesPlugin): # Save stats to compute next bitrate self.diskio_old = diskio_new - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # No standard way for the moment... pass diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py index 0aba990a..876b083d 100644 --- a/glances/plugins/glances_docker.py +++ b/glances/plugins/glances_docker.py @@ -19,7 +19,12 @@ """Docker plugin.""" +import numbers +import os +import re + # Import Glances libs +from glances.core.glances_timer import getTimeSinceLastUpdate from glances.core.glances_logging import logger from glances.plugins.glances_plugin import GlancesPlugin @@ -33,9 +38,6 @@ except ImportError as e: docker_tag = False else: docker_tag = True -import os -import re -import numbers class Plugin(GlancesPlugin): @@ -82,7 +84,6 @@ class Plugin(GlancesPlugin): # API error (Version mismatch ?) logger.debug("Docker API error (%s)" % e) # Try the connection with the server version - import re version = re.search('server\:\ (.*)\)\".*\)', str(e)) if version: logger.debug("Try connection with Docker API version %s" % version.group(1)) @@ -129,7 +130,7 @@ class Plugin(GlancesPlugin): if not docker_tag or (self.args is not None and self.args.disable_docker): return self.stats - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats # Exemple: { # "KernelVersion": "3.16.4-tinycore64", @@ -150,12 +151,31 @@ class Plugin(GlancesPlugin): # u'Names': [u'/webstack_nginx_1'], # u'Id': u'b0da859e84eb4019cf1d965b15e9323006e510352c402d2f442ea632d61faaa5'}] self.stats['containers'] = self.docker_client.containers() - # Get CPU and MEMORY stats for containers + # Get stats for all containers for c in self.stats['containers']: - c['cpu'] = self.get_docker_cpu(c['Id']) - c['memory'] = self.get_docker_memory(c['Id']) + if not hasattr(self, 'docker_stats'): + # Create a dict with all the containers' stats instance + self.docker_stats = {} - elif self.get_input() == 'snmp': + if c['Id'] not in self.docker_stats: + # Create the stats instance for the current container + try: + self.docker_stats[c['Id']] = self.docker_client.stats(c['Id'], decode=True) + logger.debug("Create Docker stats object for container {}".format(c['Id'])) + except (AttributeError, docker.errors.InvalidVersion) as e: + logger.error("Can not call Docker stats method {}".format(e)) + + # Get the docker stats + try: + all_stats = self.docker_stats[c['Id']].next() + except: + all_stats = {} + + c['cpu'] = self.get_docker_cpu(c['Id'], all_stats) + c['memory'] = self.get_docker_memory(c['Id'], all_stats) + # c['network'] = self.get_docker_network(c['Id'], all_stats) + + elif self.input_method == 'snmp': # Update stats using SNMP # Not available pass @@ -164,14 +184,14 @@ class Plugin(GlancesPlugin): return self.stats - def get_docker_cpu(self, id): + def get_docker_cpu_old(self, container_id): """Return the container CPU usage by reading /sys/fs/cgroup/... Input: id is the full container id Output: a dict {'total': 1.49, 'user': 0.65, 'system': 0.84}""" ret = {} # Read the stats try: - with open('/sys/fs/cgroup/cpuacct/docker/' + id + '/cpuacct.stat', 'r') as f: + with open('/sys/fs/cgroup/cpuacct/docker/' + container_id + '/cpuacct.stat', 'r') as f: for line in f: m = re.search(r"(system|user)\s+(\d+)", line) if m: @@ -179,23 +199,45 @@ class Plugin(GlancesPlugin): except IOError as e: logger.error("Can not grab container CPU stat ({0})".format(e)) return ret - # Get the user ticks - ticks = self.get_user_ticks() if isinstance(ret["system"], numbers.Number) and isinstance(ret["user"], numbers.Number): ret["total"] = ret["system"] + ret["user"] - for k in ret.keys(): - ret[k] = float(ret[k]) / ticks # Return the stats return ret - def get_docker_memory(self, id): + def get_docker_cpu(self, container_id, all_stats): + """Return the container CPU usage + Input: id is the full container id + all_stats is the output of the stats method of the Docker API + Output: a dict {'total': 1.49}""" + ret = {} + + # Read the stats + # try: + # ret['total'] = all_stats['cpu_stats']['cpu_usage']['total_usage'] + # except KeyError as e: + # # all_stats do not have CPU information + # logger.error("Can not grab CPU usage for container {0} ({1})".format(container_id, e)) + # # Trying fallback to old grab method + # ret = self.get_docker_cpu_old(container_id) + + # Did not work has expected, replace by the old method... + ret = self.get_docker_cpu_old(container_id) + + # Get the user ticks + ticks = self.get_user_ticks() + for k in ret.keys(): + ret[k] = float(ret[k]) / ticks + # Return the stats + return ret + + def get_docker_memory_old(self, container_id): """Return the container MEMORY usage by reading /sys/fs/cgroup/... Input: id is the full container id Output: a dict {'rss': 1015808, 'cache': 356352}""" ret = {} # Read the stats try: - with open('/sys/fs/cgroup/memory/docker/' + id + '/memory.stat', 'r') as f: + with open('/sys/fs/cgroup/memory/docker/' + container_id + '/memory.stat', 'r') as f: for line in f: m = re.search(r"(rss|cache)\s+(\d+)", line) if m: @@ -206,6 +248,73 @@ class Plugin(GlancesPlugin): # Return the stats return ret + def get_docker_memory(self, container_id, all_stats): + """Return the container MEMORY + Input: id is the full container id + all_stats is the output of the stats method of the Docker API + Output: a dict {'rss': 1015808, 'cache': 356352, 'usage': ..., 'max_usage': ...}""" + ret = {} + # Read the stats + try: + ret['rss'] = all_stats['memory_stats']['stats']['rss'] + ret['cache'] = all_stats['memory_stats']['stats']['cache'] + ret['usage'] = all_stats['memory_stats']['usage'] + ret['max_usage'] = all_stats['memory_stats']['max_usage'] + except KeyError as e: + # all_stats do not have MEM information + logger.error("Can not grab MEM usage for container {0} ({1})".format(container_id, e)) + # Trying fallback to old grab method + ret = self.get_docker_memory_old(container_id) + # Return the stats + return ret + + def get_docker_network(self, container_id, all_stats): + """Return the container network usage using the Docker API (v1.0 or higher) + Input: id is the full container id + Output: a dict {'time_since_update': 3000, 'rx': 10, 'tx': 65}""" + + # Init the returned dict + network_new = {} + + # Read the rx/tx stats (in bytes) + try: + netiocounters = all_stats["network"] + except KeyError as e: + # all_stats do not have NETWORK information + logger.debug("Can not grab NET usage for container {0} ({1})".format(container_id, e)) + # No fallback available... + return network_new + + # Previous network interface stats are stored in the network_old variable + if not hasattr(self, 'netiocounters_old'): + # First call, we init the network_old var + self.netiocounters_old = {} + try: + self.netiocounters_old[container_id] = netiocounters + except (IOError, UnboundLocalError): + pass + + if container_id not in self.netiocounters_old: + try: + self.netiocounters_old[container_id] = netiocounters + except (IOError, UnboundLocalError): + pass + else: + # By storing time data we enable Rx/s and Tx/s calculations in the + # XML/RPC API, which would otherwise be overly difficult work + # for users of the API + network_new['time_since_update'] = getTimeSinceLastUpdate('docker_net') + network_new['rx'] = netiocounters["rx_bytes"] - self.netiocounters_old[container_id]["rx_bytes"] + network_new['tx'] = netiocounters["tx_bytes"] - self.netiocounters_old[container_id]["tx_bytes"] + network_new['cumulative_rx'] = netiocounters["rx_bytes"] + network_new['cumulative_tx'] = netiocounters["tx_bytes"] + + # Save stats to compute next bitrate + self.netiocounters_old[container_id] = netiocounters + + # Return the stats + return network_new + def get_user_ticks(self): """return the user ticks by reading the environment variable""" return os.sysconf(os.sysconf_names['SC_CLK_TCK']) @@ -216,7 +325,7 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist (and non null) and display plugin enable... - if self.stats == {} or args.disable_docker or len(self.stats['containers']) == 0: + if not self.stats or args.disable_docker or len(self.stats['containers']) == 0: return ret # Build the string message @@ -239,8 +348,12 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg)) msg = '{0:>6}'.format(_("CPU%")) ret.append(self.curse_add_line(msg)) - msg = '{0:>6}'.format(_("MEM")) + msg = '{0:>7}'.format(_("MEM")) ret.append(self.curse_add_line(msg)) + # msg = '{0:>6}'.format(_("Rx/s")) + # ret.append(self.curse_add_line(msg)) + # msg = '{0:>6}'.format(_("Tx/s")) + # ret.append(self.curse_add_line(msg)) msg = ' {0:8}'.format(_("Command")) ret.append(self.curse_add_line(msg)) # Data @@ -254,7 +367,7 @@ class Plugin(GlancesPlugin): if len(name) > 20: name = '_' + name[:-19] else: - name[0:20] + name = name[:20] msg = ' {0:20}'.format(name) ret.append(self.curse_add_line(msg)) # Status @@ -270,10 +383,18 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg)) # MEM try: - msg = '{0:>6}'.format(self.auto_unit(container['memory']['rss'])) + msg = '{0:>7}'.format(self.auto_unit(container['memory']['usage'])) except KeyError: - msg = '{0:>6}'.format('?') + msg = '{0:>7}'.format('?') ret.append(self.curse_add_line(msg)) + # NET RX/TX + # for r in ['rx', 'tx']: + # try: + # value = self.auto_unit(int(container['network'][r] // container['network']['time_since_update'] * 8)) + "b" + # msg = '{0:>6}'.format(value) + # except KeyError: + # msg = '{0:>6}'.format('?') + # ret.append(self.curse_add_line(msg)) # Command msg = ' {0}'.format(container['Command']) ret.append(self.curse_add_line(msg)) diff --git a/glances/plugins/glances_fs.py b/glances/plugins/glances_fs.py index 942a9ad4..0a450d98 100644 --- a/glances/plugins/glances_fs.py +++ b/glances/plugins/glances_fs.py @@ -24,7 +24,6 @@ import operator import psutil from glances.plugins.glances_plugin import GlancesPlugin -from glances.core.glances_logging import logger # SNMP OID # The snmpd.conf needs to be edited. @@ -94,7 +93,7 @@ class Plugin(GlancesPlugin): # Reset the list self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Grab the stats using the PsUtil disk_partitions @@ -116,10 +115,6 @@ class Plugin(GlancesPlugin): # Loop over fs for fs in fs_stat: - fs_current = {} - fs_current['device_name'] = fs.device - fs_current['fs_type'] = fs.fstype - fs_current['mnt_point'] = fs.mountpoint # Grab the disk usage try: fs_usage = psutil.disk_usage(fs.mountpoint) @@ -127,52 +122,56 @@ class Plugin(GlancesPlugin): # Correct issue #346 # Disk is ejected during the command continue - fs_current['size'] = fs_usage.total - fs_current['used'] = fs_usage.used - fs_current['free'] = fs_usage.total - fs_usage.used - fs_current['percent'] = fs_usage.percent - fs_current['key'] = self.get_key() + fs_current = { + 'device_name': fs.device, + 'fs_type': fs.fstype, + 'mnt_point': fs.mountpoint, + 'size': fs_usage.total, + 'used': fs_usage.used, + 'free': fs_usage.total - fs_usage.used, + 'percent': fs_usage.percent, + 'key': self.get_key()} self.stats.append(fs_current) - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # SNMP bulk command to get all file system in one shot try: - fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True) except KeyError: - fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid['default'], + fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True) # Loop over fs - if self.get_short_system_name() in ('windows', 'esxi'): + if self.short_system_name in ('windows', 'esxi'): # Windows or ESXi tips for fs in fs_stat: - # Memory stats are grabed in the same OID table (ignore it) + # Memory stats are grabbed in the same OID table (ignore it) if fs == 'Virtual Memory' or fs == 'Physical Memory' or fs == 'Real Memory': continue - fs_current = {} - fs_current['device_name'] = '' - fs_current['mnt_point'] = fs.partition(' ')[0] - fs_current['size'] = int( - fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit']) - fs_current['used'] = int( - fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit']) - fs_current['percent'] = float( - fs_current['used'] * 100 / fs_current['size']) - fs_current['key'] = self.get_key() + size = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit']) + used = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit']) + percent = float(used * 100 / size) + fs_current = { + 'device_name': '', + 'mnt_point': fs.partition(' ')[0], + 'size': size, + 'used': used, + 'percent': percent, + 'key': self.get_key()} self.stats.append(fs_current) else: - # Default behavor + # Default behavior for fs in fs_stat: - fs_current = {} - fs_current['device_name'] = fs_stat[fs]['device_name'] - fs_current['mnt_point'] = fs - fs_current['size'] = int(fs_stat[fs]['size']) * 1024 - fs_current['used'] = int(fs_stat[fs]['used']) * 1024 - fs_current['percent'] = float(fs_stat[fs]['percent']) - fs_current['key'] = self.get_key() + fs_current = { + 'device_name': fs_stat[fs]['device_name'], + 'mnt_point': fs, + 'size': int(fs_stat[fs]['size']) * 1024, + 'used': int(fs_stat[fs]['used']) * 1024, + 'percent': float(fs_stat[fs]['percent']), + 'key': self.get_key()} self.stats.append(fs_current) # Update the history list @@ -192,7 +191,7 @@ class Plugin(GlancesPlugin): # Alert for i in self.stats: self.views[i[self.get_key()]]['used']['decoration'] = self.get_alert( - i['used'], max=i['size'], header=i['mnt_point']) + i['used'], maximum=i['size'], header=i['mnt_point']) def msg_curse(self, args=None, max_width=None): """Return the dict to display in the curse interface.""" diff --git a/glances/plugins/glances_hddtemp.py b/glances/plugins/glances_hddtemp.py index 82f29f1b..27a4740d 100644 --- a/glances/plugins/glances_hddtemp.py +++ b/glances/plugins/glances_hddtemp.py @@ -25,6 +25,7 @@ import socket # Import Glances libs from glances.plugins.glances_plugin import GlancesPlugin +from glances.core.glances_logging import logger class Plugin(GlancesPlugin): @@ -39,7 +40,7 @@ class Plugin(GlancesPlugin): GlancesPlugin.__init__(self, args=args) # Init the sensor class - self.glancesgrabhddtemp = GlancesGrabHDDTemp() + self.glancesgrabhddtemp = GlancesGrabHDDTemp(args=args) # We do not want to display the stat in a dedicated area # The HDD temp is displayed within the sensors plugin @@ -57,7 +58,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib self.stats = self.glancesgrabhddtemp.get() @@ -73,8 +74,9 @@ class GlancesGrabHDDTemp(object): """Get hddtemp stats using a socket connection.""" - def __init__(self, host='127.0.0.1', port=7634): + def __init__(self, host='127.0.0.1', port=7634, args=None): """Init hddtemp stats.""" + self.args = args self.host = host self.port = port self.cache = "" @@ -89,6 +91,10 @@ class GlancesGrabHDDTemp(object): # Reset the list self.reset() + # Only update if --disable-hddtemp is not set + if self.args is None or self.args.disable_hddtemp: + return + # Fetch the data data = self.fetch() @@ -125,7 +131,10 @@ class GlancesGrabHDDTemp(object): sck.connect((self.host, self.port)) data = sck.recv(4096) sck.close() - except socket.error: + except socket.error as e: + logger.warning("Can not connect to an HDDtemp server ({0}:{1} => {2})".format(self.host, self.port, e)) + logger.debug("Disable the HDDtemp module. Use the --disable-hddtemp to hide the previous message.") + self.args.disable_hddtemp = True data = "" return data diff --git a/glances/plugins/glances_ip.py b/glances/plugins/glances_ip.py index 200723f9..e06fac30 100644 --- a/glances/plugins/glances_ip.py +++ b/glances/plugins/glances_ip.py @@ -61,7 +61,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local' and netifaces_tag: + if self.input_method == 'local' and netifaces_tag: # Update stats using the netifaces lib try: default_gw = netifaces.gateways()['default'][netifaces.AF_INET] @@ -76,7 +76,7 @@ class Plugin(GlancesPlugin): except KeyError as e: logger.debug("Can not grab IP information (%s)".format(e)) - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Not implemented yet pass @@ -106,9 +106,11 @@ class Plugin(GlancesPlugin): return ret # Build the string message - msg = _(' - IP') + msg = _(' - ') + ret.append(self.curse_add_line(msg)) + msg = _('IP ') ret.append(self.curse_add_line(msg, 'TITLE')) - msg = ' {0:}/{1}'.format(self.stats['address'], self.stats['mask_cidr']) + msg = '{0:}/{1}'.format(self.stats['address'], self.stats['mask_cidr']) ret.append(self.curse_add_line(msg)) return ret @@ -117,4 +119,4 @@ class Plugin(GlancesPlugin): def ip_to_cidr(ip): # Convert IP address to CIDR # Exemple: '255.255.255.0' will return 24 - return sum(map(lambda x: int(x) << 8, ip.split('.'))) / 8128 + return sum(map(lambda x: int(x) << 8, ip.split('.'))) // 8128 diff --git a/glances/plugins/glances_load.py b/glances/plugins/glances_load.py index aa6e8a13..82547e54 100644 --- a/glances/plugins/glances_load.py +++ b/glances/plugins/glances_load.py @@ -76,7 +76,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Get the load using the os standard lib @@ -89,9 +89,9 @@ class Plugin(GlancesPlugin): 'min5': load[1], 'min15': load[2], 'cpucore': self.nb_log_core} - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP - self.stats = self.set_stats_snmp(snmp_oid=snmp_oid) + self.stats = self.get_stats_snmp(snmp_oid=snmp_oid) if self.stats['min1'] == '': self.reset() @@ -124,9 +124,9 @@ class Plugin(GlancesPlugin): # Add specifics informations try: # Alert and log - self.views['min15']['decoration'] = self.get_alert_log(self.stats['min15'], max=100 * self.stats['cpucore']) + self.views['min15']['decoration'] = self.get_alert_log(self.stats['min15'], maximum=100 * self.stats['cpucore']) # Alert only - self.views['min5']['decoration'] = self.get_alert(self.stats['min5'], max=100 * self.stats['cpucore']) + self.views['min5']['decoration'] = self.get_alert(self.stats['min5'], maximum=100 * self.stats['cpucore']) except KeyError: # try/except mandatory for Windows compatibility (no load stats) pass @@ -137,7 +137,7 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist... - if self.stats == {}: + if not self.stats: return ret # Build the string message diff --git a/glances/plugins/glances_mem.py b/glances/plugins/glances_mem.py index cf6ce4c5..6a1dedd6 100644 --- a/glances/plugins/glances_mem.py +++ b/glances/plugins/glances_mem.py @@ -78,7 +78,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Grab MEM using the PSUtil virtual_memory method vm_stats = psutil.virtual_memory() @@ -112,12 +112,12 @@ class Plugin(GlancesPlugin): self.stats['free'] += self.stats['cached'] # used=total-free self.stats['used'] = self.stats['total'] - self.stats['free'] - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP - if self.get_short_system_name() in ('windows', 'esxi'): + if self.short_system_name in ('windows', 'esxi'): # Mem stats for Windows|Vmware Esxi are stored in the FS table try: - fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True) except KeyError: self.reset() @@ -133,7 +133,7 @@ class Plugin(GlancesPlugin): break else: # Default behavor for others OS - self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default']) + self.stats = self.get_stats_snmp(snmp_oid=snmp_oid['default']) if self.stats['total'] == '': self.reset() @@ -167,7 +167,7 @@ class Plugin(GlancesPlugin): # Add specifics informations # Alert and log - self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], max=self.stats['total']) + self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total']) # Optional for key in ['active', 'inactive', 'buffers', 'cached']: if key in self.stats: @@ -179,7 +179,7 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist... - if self.stats == {}: + if not self.stats: return ret # Build the string message diff --git a/glances/plugins/glances_memswap.py b/glances/plugins/glances_memswap.py index 084f7596..1358e7f8 100644 --- a/glances/plugins/glances_memswap.py +++ b/glances/plugins/glances_memswap.py @@ -67,7 +67,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Grab SWAP using the PSUtil swap_memory method sm_stats = psutil.swap_memory() @@ -84,12 +84,12 @@ class Plugin(GlancesPlugin): 'sin', 'sout']: if hasattr(sm_stats, swap): self.stats[swap] = getattr(sm_stats, swap) - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP - if self.get_short_system_name() == 'windows': + if self.short_system_name == 'windows': # Mem stats for Windows OS are stored in the FS table try: - fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True) except KeyError: self.reset() @@ -109,7 +109,7 @@ class Plugin(GlancesPlugin): 'total'] - self.stats['used'] break else: - self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default']) + self.stats = self.get_stats_snmp(snmp_oid=snmp_oid['default']) if self.stats['total'] == '': self.reset() @@ -142,7 +142,7 @@ class Plugin(GlancesPlugin): # Add specifics informations # Alert and log - self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], max=self.stats['total']) + self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total']) def msg_curse(self, args=None): """Return the dict to display in the curse interface.""" @@ -150,7 +150,7 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist... - if self.stats == {}: + if not self.stats: return ret # Build the string message diff --git a/glances/plugins/glances_monitor.py b/glances/plugins/glances_monitor.py index 66765ea1..2213ca67 100644 --- a/glances/plugins/glances_monitor.py +++ b/glances/plugins/glances_monitor.py @@ -47,7 +47,7 @@ class Plugin(GlancesPlugin): def update(self): """Update the monitored list.""" - if self.get_input() == 'local': + if self.input_method == 'local': # Monitor list only available in a full Glances environment # Check if the glances_monitor instance is init if self.glances_monitors is None: diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index 0f311015..1a4f3a71 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -75,7 +75,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Grab network interface stat using the PsUtil net_io_counter method @@ -101,19 +101,21 @@ class Plugin(GlancesPlugin): network_new = netiocounters for net in network_new: try: - # Try necessary to manage dynamic network interface - netstat = {} - netstat['interface_name'] = net - netstat['time_since_update'] = time_since_update - netstat['cumulative_rx'] = network_new[net].bytes_recv - netstat['rx'] = (network_new[net].bytes_recv - - self.network_old[net].bytes_recv) - netstat['cumulative_tx'] = network_new[net].bytes_sent - netstat['tx'] = (network_new[net].bytes_sent - - self.network_old[net].bytes_sent) - netstat['cumulative_cx'] = (netstat['cumulative_rx'] + - netstat['cumulative_tx']) - netstat['cx'] = netstat['rx'] + netstat['tx'] + cumulative_rx = network_new[net].bytes_recv + cumulative_tx = network_new[net].bytes_sent + cumulative_cx = cumulative_rx + cumulative_tx + rx = cumulative_rx - self.network_old[net].bytes_recv + tx = cumulative_tx - self.network_old[net].bytes_sent + cx = rx + tx + netstat = { + 'interface_name': net, + 'time_since_update': time_since_update, + 'cumulative_rx': cumulative_rx, + 'rx': rx, + 'cumulative_tx': cumulative_tx, + 'tx': tx, + 'cumulative_cx': cumulative_cx, + 'cx': cx} except KeyError: continue else: @@ -123,15 +125,15 @@ class Plugin(GlancesPlugin): # Save stats to compute next bitrate self.network_old = network_new - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # SNMP bulk command to get all network interface in one shot try: - netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True) except KeyError: - netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid['default'], + netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True) # Previous network interface stats are stored in the network_old variable @@ -150,27 +152,30 @@ class Plugin(GlancesPlugin): for net in network_new: try: - # Try necessary to manage dynamic network interface - netstat = {} # Windows: a tips is needed to convert HEX to TXT # http://blogs.technet.com/b/networking/archive/2009/12/18/how-to-query-the-list-of-network-interfaces-using-snmp-via-the-ifdescr-counter.aspx - if self.get_short_system_name() == 'windows': + if self.short_system_name == 'windows': try: - netstat['interface_name'] = str(base64.b16decode(net[2:-2].upper())) + interface_name = str(base64.b16decode(net[2:-2].upper())) except TypeError: - netstat['interface_name'] = net + interface_name = net else: - netstat['interface_name'] = net - netstat['time_since_update'] = time_since_update - netstat['cumulative_rx'] = float(network_new[net]['cumulative_rx']) - netstat['rx'] = (float(network_new[net]['cumulative_rx']) - - float(self.network_old[net]['cumulative_rx'])) - netstat['cumulative_tx'] = float(network_new[net]['cumulative_tx']) - netstat['tx'] = (float(network_new[net]['cumulative_tx']) - - float(self.network_old[net]['cumulative_tx'])) - netstat['cumulative_cx'] = (netstat['cumulative_rx'] + - netstat['cumulative_tx']) - netstat['cx'] = netstat['rx'] + netstat['tx'] + interface_name = net + cumulative_rx = float(network_new[net]['cumulative_rx']) + cumulative_tx = float(network_new[net]['cumulative_tx']) + cumulative_cx = cumulative_rx + cumulative_tx + rx = cumulative_rx - float(self.network_old[net]['cumulative_rx']) + tx = cumulative_tx - float(self.network_old[net]['cumulative_tx']) + cx = rx + tx + netstat = { + 'interface_name': interface_name, + 'time_since_update': time_since_update, + 'cumulative_rx': cumulative_rx, + 'rx': rx, + 'cumulative_tx': cumulative_tx, + 'tx': tx, + 'cumulative_cx': cumulative_cx, + 'cx': cx} except KeyError: continue else: diff --git a/glances/plugins/glances_now.py b/glances/plugins/glances_now.py index b40fa44f..71c83c8f 100644 --- a/glances/plugins/glances_now.py +++ b/glances/plugins/glances_now.py @@ -39,7 +39,7 @@ class Plugin(GlancesPlugin): self.display_curse = True # Set the message position - self.set_align('bottom') + self.align = 'bottom' def update(self): """Update current date/time.""" diff --git a/glances/plugins/glances_percpu.py b/glances/plugins/glances_percpu.py index 93abeab6..62a9b137 100644 --- a/glances/plugins/glances_percpu.py +++ b/glances/plugins/glances_percpu.py @@ -53,7 +53,7 @@ class Plugin(GlancesPlugin): # Grab per-CPU stats using psutil's cpu_percent(percpu=True) and # cpu_times_percent(percpu=True) methods - if self.get_input() == 'local': + if self.input_method == 'local': percpu_percent = psutil.cpu_percent(interval=0.0, percpu=True) percpu_times_percent = psutil.cpu_times_percent(interval=0.0, percpu=True) for cputimes in percpu_times_percent: diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 7ff575f6..205df2d9 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -49,11 +49,11 @@ class GlancesPlugin(object): self.args = args # Init the default alignement (for curses) - self.set_align('left') + self._align = 'left' # Init the input method - self.input_method = 'local' - self.short_system_name = None + self._input_method = 'local' + self._short_system_name = None # Init the stats list self.stats = None @@ -63,7 +63,7 @@ class GlancesPlugin(object): self.stats_history = self.init_stats_history() # Init the limits dictionnary - self.limits = dict() + self._limits = dict() # Init the actions self.actions = GlancesActions() @@ -107,11 +107,12 @@ class GlancesPlugin(object): logger.debug("Reset history for plugin {0} (items: {0})".format( self.plugin_name, reset_list)) self.stats_history = {} - return self.stats_history def update_stats_history(self, item_name=''): """Update stats history""" - if self.stats != [] and self.args is not None and self.args.enable_history and self.get_items_history_list() is not None: + if (self.stats and self.args is not None and + self.args.enable_history and + self.get_items_history_list() is not None): self.add_item_history('date', datetime.now()) for i in self.get_items_history_list(): if type(self.stats) is list: @@ -125,7 +126,6 @@ class GlancesPlugin(object): # Stats is not a list # Add the item to the history directly self.add_item_history(i['name'], self.stats[i['name']]) - return self.stats_history def get_stats_history(self): """Return the stats history""" @@ -135,37 +135,42 @@ class GlancesPlugin(object): """Return the items history list""" return self.items_history_list - def set_input(self, input_method, short_system_name=None): + @property + def input_method(self): + """Get the input method.""" + return self._input_method + + @input_method.setter + def input_method(self, input_method): """Set the input method. * local: system local grab (psutil or direct access) * snmp: Client server mode via SNMP * glances: Client server mode via Glances API - - For SNMP, short_system_name is detected short OS name """ - self.input_method = input_method - self.short_system_name = short_system_name - return self.input_method + self._input_method = input_method - def get_input(self): - """Get the input method.""" - return self.input_method + @property + def short_system_name(self): + """Get the short detected OS name (SNMP).""" + return self._short_system_name - def get_short_system_name(self): - """Get the short detected OS name""" - return self.short_system_name + @short_system_name.setter + def short_system_name(self, short_name): + """Set the short detected OS name (SNMP).""" + self._short_system_name = short_name def set_stats(self, input_stats): """Set the stats to input_stats.""" self.stats = input_stats - return self.stats - def set_stats_snmp(self, bulk=False, snmp_oid={}): + def get_stats_snmp(self, bulk=False, snmp_oid=None): """Update stats using SNMP. If bulk=True, use a bulk request instead of a get request. """ + snmp_oid = snmp_oid or {} + from glances.core.glances_snmp import GlancesSNMPClient # Init the SNMP request @@ -201,7 +206,7 @@ class GlancesPlugin(object): item_key = item[oid] else: item_stats[key] = item[oid] - if item_stats != {}: + if item_stats: ret[item_key] = item_stats index += 1 else: @@ -299,7 +304,6 @@ class GlancesPlugin(object): def set_views(self, input_views): """Set the views to input_views.""" self.views = input_views - return self.views def get_views(self, item=None, key=None, option=None): """Return the views object. @@ -326,27 +330,26 @@ class GlancesPlugin(object): """Load the limits from the configuration file.""" if (hasattr(config, 'has_section') and config.has_section(self.plugin_name)): - for s, v in config.items(self.plugin_name): + for level, v in config.items(self.plugin_name): # Read limits + limit = '_'.join([self.plugin_name, level]) try: - self.limits[ - self.plugin_name + '_' + s] = config.get_option(self.plugin_name, s) + self._limits[limit] = config.get_option(self.plugin_name, level) except ValueError: - self.limits[ - self.plugin_name + '_' + s] = config.get_raw_option(self.plugin_name, s).split(",") - logger.debug("Load limit: {0} = {1}".format(self.plugin_name + '_' + s, - self.limits[self.plugin_name + '_' + s])) + self._limits[limit] = config.get_raw_option(self.plugin_name, level).split(",") + logger.debug("Load limit: {0} = {1}".format(limit, self._limits[limit])) - def set_limits(self, input_limits): - """Set the limits to input_limits.""" - self.limits = input_limits - return self.limits - - def get_limits(self): + @property + def limits(self): """Return the limits object.""" - return self.limits + return self._limits - def get_alert(self, current=0, min=0, max=100, header="", log=False): + @limits.setter + def limits(self, input_limits): + """Set the limits to input_limits.""" + self._limits = input_limits + + def get_alert(self, current=0, minimum=0, maximum=100, header="", log=False): """Return the alert status relative to a current value. Use this function for minor stats. @@ -365,7 +368,7 @@ class GlancesPlugin(object): """ # Compute the % try: - value = (current * 100) / max + value = (current * 100) / maximum except ZeroDivisionError: return 'DEFAULT' except TypeError: @@ -386,7 +389,7 @@ class GlancesPlugin(object): ret = 'WARNING' elif value > self.__get_limit('careful', stat_name=stat_name): ret = 'CAREFUL' - elif current < min: + elif current < minimum: ret = 'CAREFUL' except KeyError: return 'DEFAULT' @@ -428,20 +431,20 @@ class GlancesPlugin(object): # Default is ok return ret + log_str - def get_alert_log(self, current=0, min=0, max=100, header=""): + def get_alert_log(self, current=0, minimum=0, maximum=100, header=""): """Get the alert log.""" - return self.get_alert(current, min, max, header, log=True) + return self.get_alert(current, minimum, maximum, header, log=True) def __get_limit(self, criticity, stat_name=""): """Return the limit value for the alert""" # Get the limit for stat + header # Exemple: network_wlan0_rx_careful try: - limit = self.limits[stat_name + '_' + criticity] + limit = self._limits[stat_name + '_' + criticity] except KeyError: # Try fallback to plugin default limit # Exemple: network_careful - limit = self.limits[self.plugin_name + '_' + criticity] + limit = self._limits[self.plugin_name + '_' + criticity] # Return the limit return limit @@ -451,11 +454,11 @@ class GlancesPlugin(object): # Get the action for stat + header # Exemple: network_wlan0_rx_careful_action try: - ret = self.limits[stat_name + '_' + criticity + '_action'] + ret = self._limits[stat_name + '_' + criticity + '_action'] except KeyError: # Try fallback to plugin default limit # Exemple: network_careful_action - ret = self.limits[self.plugin_name + '_' + criticity + '_action'] + ret = self._limits[self.plugin_name + '_' + criticity + '_action'] # Return the action list return ret @@ -465,12 +468,12 @@ class GlancesPlugin(object): # Get the log tag for stat + header # Exemple: network_wlan0_rx_log try: - log_tag = self.limits[stat_name + '_log'] + log_tag = self._limits[stat_name + '_log'] except KeyError: # Try fallback to plugin default log # Exemple: network_log try: - log_tag = self.limits[self.plugin_name + '_log'] + log_tag = self._limits[self.plugin_name + '_log'] except KeyError: # By defaukt, log are disabled return default_action @@ -484,12 +487,12 @@ class GlancesPlugin(object): plugin_name = self.plugin_name if header == "": try: - return self.limits[plugin_name + '_' + value] + return self._limits[plugin_name + '_' + value] except KeyError: return [] else: try: - return self.limits[plugin_name + '_' + header + '_' + value] + return self._limits[plugin_name + '_' + header + '_' + value] except KeyError: return [] @@ -500,7 +503,7 @@ class GlancesPlugin(object): def has_alias(self, header): """Return the alias name for the relative header or None if nonexist""" try: - return self.limits[self.plugin_name + '_' + header + '_' + 'alias'][0] + return self._limits[self.plugin_name + '_' + header + '_' + 'alias'][0] except (KeyError, IndexError): return None @@ -522,7 +525,7 @@ class GlancesPlugin(object): if hasattr(self, 'display_curse'): display_curse = self.display_curse if hasattr(self, 'align'): - align_curse = self.align + align_curse = self._align if max_width is not None: ret = {'display': display_curse, @@ -568,16 +571,18 @@ class GlancesPlugin(object): """Go to a new line.""" return self.curse_add_line('\n') - def set_align(self, align='left'): - """Set the Curse align""" - if align in ('left', 'right', 'bottom'): - self.align = align - else: - self.align = 'left' + @property + def align(self): + """Get the curse align.""" + return self._align - def get_align(self): - """Get the Curse align""" - return self.align + @align.setter + def align(self, value): + """Set the curse align. + + value: left, right, bottom. + """ + self._align = value def auto_unit(self, number, low_precision=False): """Make a nice human-readable string out of number. diff --git a/glances/plugins/glances_processcount.py b/glances/plugins/glances_processcount.py index 3248ef1d..1d3486c4 100644 --- a/glances/plugins/glances_processcount.py +++ b/glances/plugins/glances_processcount.py @@ -52,14 +52,14 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Here, update is call for processcount AND processlist glances_processes.update() # Return the processes count self.stats = glances_processes.getcount() - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # !!! TODO pass @@ -77,14 +77,14 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg)) return ret - if self.stats == {}: + if not self.stats: return ret # Display the filter (if it exists) - if glances_processes.get_process_filter() is not None: + if glances_processes.process_filter is not None: msg = _("Processes filter:") ret.append(self.curse_add_line(msg, "TITLE")) - msg = _(" {0} ").format(glances_processes.get_process_filter()) + msg = _(" {0} ").format(glances_processes.process_filter) ret.append(self.curse_add_line(msg, "FILTER")) msg = _("(press ENTER to edit)") ret.append(self.curse_add_line(msg)) @@ -117,13 +117,13 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg)) # Display sort information - if glances_processes.getmanualsortkey() is None: + if glances_processes.auto_sort: msg = _("sorted automatically") ret.append(self.curse_add_line(msg)) - msg = _(" by {0}").format(glances_processes.getautosortkey()) + msg = _(" by {0}").format(glances_processes.sort_key) ret.append(self.curse_add_line(msg)) else: - msg = _("sorted by {0}").format(glances_processes.getmanualsortkey()) + msg = _("sorted by {0}").format(glances_processes.sort_key) ret.append(self.curse_add_line(msg)) ret[-1]["msg"] += ", %s view" % ("tree" if glances_processes.is_tree_enabled() else "flat") diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 86999226..902cbef9 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -59,7 +59,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib # Note: Update is done in the processcount plugin # Just return the processes list @@ -67,7 +67,7 @@ class Plugin(GlancesPlugin): self.stats = glances_processes.gettree() else: self.stats = glances_processes.getlist() - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # No SNMP grab for processes pass @@ -154,8 +154,7 @@ class Plugin(GlancesPlugin): def get_process_curses_data(self, p, first, args): """ Get curses data to display for a process. """ - ret = [] - ret.append(self.curse_new_line()) + ret = [self.curse_new_line()] # CPU if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '': msg = '{0:>6.1f}'.format(p['cpu_percent']) @@ -365,7 +364,7 @@ class Plugin(GlancesPlugin): return ret # Compute the sort key - process_sort_key = glances_processes.getsortkey() + process_sort_key = glances_processes.sort_key sort_style = 'SORT' # Header @@ -401,7 +400,7 @@ class Plugin(GlancesPlugin): ret.extend(self.get_process_tree_curses_data(self.sortstats(process_sort_key), args, first_level=True, - max_node_count=glances_processes.get_max_processes())) + max_node_count=glances_processes.max_processes)) else: # Loop over processes (sorted by the sort key previously compute) first = True diff --git a/glances/plugins/glances_psutilversion.py b/glances/plugins/glances_psutilversion.py index 0c2ffa14..bfdbe878 100644 --- a/glances/plugins/glances_psutilversion.py +++ b/glances/plugins/glances_psutilversion.py @@ -45,7 +45,7 @@ class Plugin(GlancesPlugin): self.reset() # Return PsUtil version as a tuple - if self.get_input() == 'local': + if self.input_method == 'local': # PsUtil version only available in local try: self.stats = tuple([int(num) for num in __psutil_version.split('.')]) diff --git a/glances/plugins/glances_quicklook.py b/glances/plugins/glances_quicklook.py index 1c76167c..7df0fddb 100644 --- a/glances/plugins/glances_quicklook.py +++ b/glances/plugins/glances_quicklook.py @@ -24,7 +24,7 @@ import psutil from glances.plugins.glances_plugin import GlancesPlugin from glances.core.glances_cpu_percent import cpu_percent from glances.outputs.glances_bars import Bar -from glances.core.glances_logging import logger +#from glances.core.glances_logging import logger class Plugin(GlancesPlugin): @@ -55,13 +55,13 @@ class Plugin(GlancesPlugin): self.reset() # Grab quicklook stats: CPU, MEM and SWAP - if self.get_input() == 'local': + if self.input_method == 'local': # Get the latest CPU percent value self.stats['cpu'] = cpu_percent.get() # Use the PsUtil lib for the memory (virtual and swap) self.stats['mem'] = psutil.virtual_memory().percent self.stats['swap'] = psutil.swap_memory().percent - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Not available pass @@ -87,7 +87,7 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist... - if self.stats == {} or args.disable_quicklook: + if not self.stats or args.disable_quicklook: return ret # Define the bar @@ -95,7 +95,7 @@ class Plugin(GlancesPlugin): # Build the string message for key in ['cpu', 'mem', 'swap']: - bar.set_percent(self.stats[key]) + bar.percent = self.stats[key] msg = '{0:>4} '.format(key.upper()) ret.append(self.curse_add_line(msg)) msg = '{0}'.format(bar) diff --git a/glances/plugins/glances_raid.py b/glances/plugins/glances_raid.py index a31baccf..007bd467 100644 --- a/glances/plugins/glances_raid.py +++ b/glances/plugins/glances_raid.py @@ -28,7 +28,6 @@ try: from pymdstat import MdStat except ImportError: logger.debug("pymdstat library not found. Glances cannot grab RAID info.") - pass class Plugin(GlancesPlugin): @@ -58,7 +57,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the PyMDstat lib (https://github.com/nicolargo/pymdstat) try: mds = MdStat() @@ -67,7 +66,7 @@ class Plugin(GlancesPlugin): logger.debug("Can not grab RAID stats (%s)" % e) return self.stats - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # No standard way for the moment... pass diff --git a/glances/plugins/glances_sensors.py b/glances/plugins/glances_sensors.py index b43aaf7e..18a1856f 100644 --- a/glances/plugins/glances_sensors.py +++ b/glances/plugins/glances_sensors.py @@ -83,7 +83,7 @@ class Plugin(GlancesPlugin): # Reset the stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the dedicated lib self.stats = [] # Get the temperature @@ -123,7 +123,7 @@ class Plugin(GlancesPlugin): # Append Batteries % self.stats.extend(batpercent) - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP # No standard: # http://www.net-snmp.org/wiki/index.php/Net-SNMP_and_lm-sensors_on_Ubuntu_10.04 @@ -156,7 +156,7 @@ class Plugin(GlancesPlugin): # Add specifics informations # Alert for i in self.stats: - if i['value'] == []: + if not i['value']: continue if i['type'] == 'battery': self.views[i[self.get_key()]]['value']['decoration'] = self.get_alert(100 - i['value'], header=i['type']) @@ -169,7 +169,7 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist and display plugin enable... - if not self.stats or args.disable_sensors or self.stats == []: + if not self.stats or args.disable_sensors: return ret # Build the string message @@ -178,7 +178,7 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg, "TITLE")) for i in self.stats: - if i['value'] is not None and i['value'] != []: + if i['value']: # New line ret.append(self.curse_new_line()) # Alias for the lable name ? @@ -187,8 +187,8 @@ class Plugin(GlancesPlugin): label = i['label'] try: msg = "{0:12} {1:3}".format(label[:11], i['unit']) - except KeyError: - msg = '{0:16}'.format(label[:15]) + except (KeyError, UnicodeEncodeError): + msg = "{0:16}".format(label[:15]) ret.append(self.curse_add_line(msg)) msg = '{0:>7}'.format(i['value']) ret.append(self.curse_add_line( @@ -234,23 +234,23 @@ class GlancesGrabSensors(object): elif feature.name.startswith(b'fan'): # Fan speed sensor sensors_current['unit'] = SENSOR_FAN_UNIT - if sensors_current != {}: + if sensors_current: sensors_current['label'] = feature.label sensors_current['value'] = int(feature.get_value()) self.sensors_list.append(sensors_current) return self.sensors_list - def get(self, type='temperature_core'): + def get(self, sensor_type='temperature_core'): """Get sensors list.""" self.__update__() - if type == 'temperature_core': + if sensor_type == 'temperature_core': ret = [s for s in self.sensors_list if s['unit'] == SENSOR_TEMP_UNIT] - elif type == 'fan_speed': + elif sensor_type == 'fan_speed': ret = [s for s in self.sensors_list if s['unit'] == SENSOR_FAN_UNIT] else: # Unknown type - logger.debug("Unknown sensor type %s" % type) + logger.debug("Unknown sensor type %s" % sensor_type) ret = [] return ret diff --git a/glances/plugins/glances_system.py b/glances/plugins/glances_system.py index 2af66bcf..cb9c73e4 100644 --- a/glances/plugins/glances_system.py +++ b/glances/plugins/glances_system.py @@ -103,7 +103,7 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib self.stats['os_name'] = platform.system() self.stats['hostname'] = platform.node() @@ -136,17 +136,17 @@ class Plugin(GlancesPlugin): self.stats['os_name'], self.stats['os_version']) self.stats['hr_name'] += ' ({0})'.format(self.stats['platform']) - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP try: - self.stats = self.set_stats_snmp( - snmp_oid=snmp_oid[self.get_short_system_name()]) + self.stats = self.get_stats_snmp( + snmp_oid=snmp_oid[self.short_system_name]) except KeyError: - self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default']) + self.stats = self.get_stats_snmp(snmp_oid=snmp_oid['default']) # Default behavor: display all the information self.stats['os_name'] = self.stats['system_name'] # Windows OS tips - if self.get_short_system_name() == 'windows': + if self.short_system_name == 'windows': try: iteritems = snmp_to_human['windows'].iteritems() except AttributeError: diff --git a/glances/plugins/glances_uptime.py b/glances/plugins/glances_uptime.py index 12e1c22b..764c9d08 100644 --- a/glances/plugins/glances_uptime.py +++ b/glances/plugins/glances_uptime.py @@ -47,7 +47,7 @@ class Plugin(GlancesPlugin): self.display_curse = True # Set the message position - self.set_align('right') + self.align = 'right' # Init the stats self.reset() @@ -61,16 +61,15 @@ class Plugin(GlancesPlugin): # Reset stats self.reset() - if self.get_input() == 'local': + if self.input_method == 'local': # Update stats using the standard system lib - uptime = datetime.now() - \ - datetime.fromtimestamp(psutil.boot_time()) + uptime = datetime.now() - datetime.fromtimestamp(psutil.boot_time()) # Convert uptime to string (because datetime is not JSONifi) self.stats = str(uptime).split('.')[0] - elif self.get_input() == 'snmp': + elif self.input_method == 'snmp': # Update stats using SNMP - uptime = self.set_stats_snmp(snmp_oid=snmp_oid)['_uptime'] + uptime = self.get_stats_snmp(snmp_oid=snmp_oid)['_uptime'] try: # In hundredths of seconds self.stats = str(timedelta(seconds=int(uptime) / 100)) @@ -82,11 +81,4 @@ class Plugin(GlancesPlugin): def msg_curse(self, args=None): """Return the string to display in the curse interface.""" - # Init the return message - ret = [] - - # Add the line with decoration - ret.append(self.curse_add_line(_("Uptime: {0}").format(self.stats))) - - # Return the message with decoration - return ret + return [self.curse_add_line(_("Uptime: {0}").format(self.stats))] diff --git a/man/glances.1 b/man/glances.1 index 3a717659..6f980503 100644 --- a/man/glances.1 +++ b/man/glances.1 @@ -49,6 +49,10 @@ disable network module .B \-\-disable-sensors disable sensors module .TP +.B \-\-disable-hddtemp +disable HDDTemp module +.TP +.TP .B \-\-disable-left-sidebar disable network, disk IO, FS and sensors modules .TP @@ -121,6 +125,12 @@ set refresh time in seconds [default: 3 sec] .B \-w, \-\-webserver run Glances in Web server mode .TP +.B \-q, \-\-quiet +run Glances in quiet mode (nothing is displayed) +.TP +.B -\f PROCESS_FILTER, \-\-process\-filter PROCESS_FILTER +set the process filter patern (regular expression) +.TP .B \-1, \-\-percpu start Glances in per CPU mode .TP @@ -210,9 +220,31 @@ Show/hide processes list (for low CPU consumption) Switch between global CPU and per-CPU stats .SH EXAMPLES .TP -Refresh information every 5 seconds: -.B glances -\-t 5 +Monitor local machine (standalone mode): +.B $ glances +.PP +Monitor local machine with the Web interface (Web UI): +.B $ glances -w +.PP +Glances web server started on http://0.0.0.0:61208/ +.PP +Monitor local machine and export stats to a CSV file (standalone mode): +.B $ glances --export-csv +.PP +Monitor local machine and export stats to a InfluxDB server with 5s refresh time (standalone mode): +.B $ glances -t 5 --export-influxdb +.PP +Start a Glances server (server mode): +.B $ glances -s +.PP +Connect Glances to a Glances server (client mode): +.B $ glances -c +.PP +Connect Glances to a Glances server and export stats to a StatsD server (client mode): +.B $ glances -c --export-statsd +.PP +Start the client browser (browser mode): +.B $ glances --browser .PP .SH EXIT STATUS Glances returns a zero exit status if it succeeds to print/grab information. diff --git a/requirements.txt b/requirements.txt index 7a795604..914d2457 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -psutil==2.2.0 +psutil==2.2.1 diff --git a/setup.py b/setup.py index f598e2bf..78192091 100755 --- a/setup.py +++ b/setup.py @@ -8,6 +8,9 @@ from setuptools import setup is_chroot = os.stat('/').st_ino != 2 +if sys.version_info < (2, 6) or (3, 0) <= sys.version_info < (3, 3): + print('Glances requires at least Python 2.6 or 3.3 to run.') + sys.exit(1) def get_data_files(): data_files = [ @@ -31,7 +34,6 @@ def get_data_files(): conf_path = os.path.join(os.environ.get('APPDATA'), 'glances') else: # Unix-like + per-user install conf_path = os.path.join('etc', 'glances') - data_files.append((conf_path, ['conf/glances.conf'])) for mo in glob.glob('i18n/*/LC_MESSAGES/*.mo'): @@ -45,8 +47,8 @@ def get_requires(): requires = ['psutil>=2.0.0'] if sys.platform.startswith('win'): requires += ['colorconsole'] - if sys.version_info < (2, 7): - requires += ['argparse'] + if sys.version_info == (2, 6): + requires += ['argparse', 'logutils'] return requires @@ -71,7 +73,7 @@ setup( 'BROWSER': ['zeroconf>=0.16', 'netifaces'], 'RAID': ['pymdstat'], 'DOCKER': ['docker-py'], - 'EXPORT': ['influxdb', 'statsd'], + 'EXPORT': ['influxdb>=1.0.0', 'statsd'], 'ACTION': ['pystache'] }, packages=['glances'], diff --git a/sonar-project.properties b/sonar-project.properties index 226c9e76..726f5926 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ # Required metadata sonar.projectKey=glances sonar.projectName=Glances -sonar.projectVersion=2.3_beta +sonar.projectVersion=2.4_beta # Path to the parent source code directory. # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. @@ -17,4 +17,4 @@ sonar.language=py sonar.sourceEncoding=UTF-8 # Additional parameters -#sonar.my.property=value \ No newline at end of file +#sonar.my.property=value diff --git a/unitest-restful.py b/unitest-restful.py index 55fb5292..74a6e18d 100755 --- a/unitest-restful.py +++ b/unitest-restful.py @@ -91,7 +91,7 @@ class TestGlances(unittest.TestCase): args = shlex.split(cmdline) pid = subprocess.Popen(args) print("Please wait...") - time.sleep(1) + time.sleep(3) self.assertTrue(pid is not None) diff --git a/unitest.py b/unitest.py index d01693c7..123783e4 100755 --- a/unitest.py +++ b/unitest.py @@ -78,14 +78,14 @@ class TestGlances(unittest.TestCase): print('INFO: [TEST_000] Test the stats update function') try: stats.update() - except: - print('ERROR: Stats update failed') + except Exception as e: + print('ERROR: Stats update failed ({})'.format(e)) self.assertTrue(False) time.sleep(1) try: stats.update() - except: - print('ERROR: Stats update failed') + except Exception as e: + print('ERROR: Stats update failed ({})'.format(e)) self.assertTrue(False) self.assertTrue(True)