mirror of
https://github.com/kubernetes/node-problem-detector.git
synced 2026-08-23 22:26:27 +00:00
Merge pull request #410 from xueweiz/stats
Collect more CPU/disk/memory metrics
This commit is contained in:
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"cpu": {
|
||||
"metricsConfigs": {
|
||||
"cpu/runnable_task_count": {
|
||||
"displayName": "cpu/runnable_task_count"
|
||||
},
|
||||
"cpu/usage_time": {
|
||||
"displayName": "cpu/usage_time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disk": {
|
||||
"metricsConfigs": {
|
||||
"disk/io_time": {
|
||||
@@ -9,6 +19,21 @@
|
||||
},
|
||||
"disk/avg_queue_len": {
|
||||
"displayName": "disk/avg_queue_len"
|
||||
},
|
||||
"disk/operation_count": {
|
||||
"displayName": "disk/operation_count"
|
||||
},
|
||||
"disk/merged_operation_count": {
|
||||
"displayName": "disk/merged_operation_count"
|
||||
},
|
||||
"disk/operation_bytes_count": {
|
||||
"displayName": "disk/operation_bytes_count"
|
||||
},
|
||||
"disk/operation_time": {
|
||||
"displayName": "disk/operation_time"
|
||||
},
|
||||
"disk/bytes_used": {
|
||||
"displayName": "disk/bytes_used"
|
||||
}
|
||||
},
|
||||
"includeRootBlk": true,
|
||||
@@ -22,5 +47,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"memory": {
|
||||
"metricsConfigs": {
|
||||
"memory/bytes_used": {
|
||||
"displayName": "memory/bytes_used"
|
||||
},
|
||||
"memory/anonymous_used": {
|
||||
"displayName": "memory/anonymous_used"
|
||||
},
|
||||
"memory/page_cache_used": {
|
||||
"displayName": "memory/page_cache_used"
|
||||
},
|
||||
"memory/unevictable_used": {
|
||||
"displayName": "memory/unevictable_used"
|
||||
},
|
||||
"memory/dirty_used": {
|
||||
"displayName": "memory/dirty_used"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invokeInterval": "60s"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ require (
|
||||
github.com/pborman/uuid v1.2.0
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
|
||||
github.com/prometheus/common v0.4.1
|
||||
github.com/shirou/gopsutil v2.18.12+incompatible
|
||||
github.com/prometheus/procfs v0.0.8
|
||||
github.com/shirou/gopsutil v2.19.12+incompatible
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
|
||||
github.com/sigma/go-inotify v0.0.0-20181102212354-c87b6cf5033d // indirect
|
||||
github.com/spf13/pflag v1.0.3
|
||||
|
||||
@@ -297,6 +297,8 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY78=
|
||||
github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
@@ -304,6 +306,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||
github.com/satori/go.uuid v0.0.0-20160713180306-0aa62d5ddceb/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
|
||||
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v2.19.12+incompatible h1:WRstheAymn1WOPesh+24+bZKFkqrdCR8JOc77v4xV3Q=
|
||||
github.com/shirou/gopsutil v2.19.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20180925043049-51d7b505e2e9/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
@@ -407,6 +411,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -47,12 +47,24 @@ func init() {
|
||||
const exporterName = "stackdriver"
|
||||
|
||||
var NPDMetricToSDMetric = map[metrics.MetricID]string{
|
||||
metrics.HostUptimeID: "compute.googleapis.com/guest/system/uptime",
|
||||
metrics.ProblemCounterID: "compute.googleapis.com/guest/system/problem_count",
|
||||
metrics.ProblemGaugeID: "compute.googleapis.com/guest/system/problem_state",
|
||||
metrics.DiskAvgQueueLenID: "compute.googleapis.com/guest/disk/queue_length",
|
||||
metrics.DiskIOTimeID: "compute.googleapis.com/guest/disk/io_time",
|
||||
metrics.DiskWeightedIOID: "compute.googleapis.com/guest/disk/weighted_io_time",
|
||||
metrics.CPURunnableTaskCountID: "compute.googleapis.com/guest/cpu/runnable_task_count",
|
||||
metrics.CPUUsageTimeID: "compute.googleapis.com/guest/cpu/usage_time",
|
||||
metrics.DiskAvgQueueLenID: "compute.googleapis.com/guest/disk/queue_length",
|
||||
metrics.DiskBytesUsedID: "compute.googleapis.com/guest/disk/bytes_used",
|
||||
metrics.DiskIOTimeID: "compute.googleapis.com/guest/disk/io_time",
|
||||
metrics.DiskMergedOpsCountID: "compute.googleapis.com/guest/disk/merged_operation_count",
|
||||
metrics.DiskOpsBytesID: "compute.googleapis.com/guest/disk/operation_bytes_count",
|
||||
metrics.DiskOpsCountID: "compute.googleapis.com/guest/disk/operation_count",
|
||||
metrics.DiskOpsTimeID: "compute.googleapis.com/guest/disk/operation_time",
|
||||
metrics.DiskWeightedIOID: "compute.googleapis.com/guest/disk/weighted_io_time",
|
||||
metrics.HostUptimeID: "compute.googleapis.com/guest/system/uptime",
|
||||
metrics.MemoryAnonymousUsedID: "compute.googleapis.com/guest/memory/anonymous_used",
|
||||
metrics.MemoryBytesUsedID: "compute.googleapis.com/guest/memory/bytes_used",
|
||||
metrics.MemoryDirtyUsedID: "compute.googleapis.com/guest/memory/dirty_used",
|
||||
metrics.MemoryPageCacheUsedID: "compute.googleapis.com/guest/memory/page_cache_used",
|
||||
metrics.MemoryUnevictableUsedID: "compute.googleapis.com/guest/memory/unevictable_used",
|
||||
metrics.ProblemCounterID: "compute.googleapis.com/guest/system/problem_count",
|
||||
metrics.ProblemGaugeID: "compute.googleapis.com/guest/system/problem_state",
|
||||
}
|
||||
|
||||
func getMetricTypeConversionFunction(customMetricPrefix string) func(*view.View) string {
|
||||
|
||||
@@ -4,27 +4,67 @@
|
||||
|
||||
Currently supported components are:
|
||||
|
||||
* cpu
|
||||
* disk
|
||||
* host
|
||||
* memory
|
||||
|
||||
See example config file [here](https://github.com/kubernetes/node-problem-detector/blob/master/config/system-stats-monitor.json).
|
||||
|
||||
By setting the `metricsConfigs` field and `displayName` field ([example](https://github.com/kubernetes/node-problem-detector/blob/master/config/system-stats-monitor.json)), you can specify the list of metrics to be collected, and their display names on the Prometheus scaping endpoint.
|
||||
|
||||
## Detailed Configuration Options
|
||||
|
||||
### Global Configurations
|
||||
|
||||
Data collection period can be specified globally in the config file, see `invokeInterval` at the [example](https://github.com/kubernetes/node-problem-detector/blob/master/config/system-stats-monitor.json).
|
||||
|
||||
### CPU
|
||||
|
||||
Below metrics are collected from `cpu` component:
|
||||
|
||||
* `cpu_runnable_task_count`: The average number of runnable tasks in the run-queue during the last minute. Collected from [`/proc/loadavg`][/proc doc].
|
||||
* `cpu_usage_time`: CPU usage, in seconds. The [CPU state][/proc doc] for the corresponding usage is reported under the `state` metric label (e.g. `user`, `nice`, `system`...).
|
||||
|
||||
[/proc doc]: http://man7.org/linux/man-pages/man5/proc.5.html
|
||||
|
||||
### Disk
|
||||
|
||||
Below metrics are collected from `disk` component:
|
||||
|
||||
* `disk/io_time`: [# of milliseconds spent doing I/Os on this device](https://www.kernel.org/doc/Documentation/iostats.txt)
|
||||
* `disk/weighted_io`: [# of milliseconds spent doing I/Os on this device](https://www.kernel.org/doc/Documentation/iostats.txt)
|
||||
* `disk/avg_queue_len`: [average # of requests that was waiting in queue or being serviced during the last `invokeInterval`](https://www.xaprb.com/blog/2010/01/09/how-linux-iostat-computes-its-results/)
|
||||
* `disk_io_time`: [# of milliseconds spent doing I/Os on this device][iostat doc]
|
||||
* `disk_weighted_io`: [# of milliseconds spent doing I/Os on this device][iostat doc]
|
||||
* `disk_avg_queue_len`: [average # of requests that was waiting in queue or being serviced during the last `invokeInterval`](https://www.xaprb.com/blog/2010/01/09/how-linux-iostat-computes-its-results/)
|
||||
* `disk_operation_count`: [# of reads/writes completed][iostat doc]
|
||||
* `disk_merged_operation_count`: [# of reads/writes merged][iostat doc]
|
||||
* `disk_operation_bytes_count`: # of Bytes used for reads/writes on this device
|
||||
* `disk_operation_time`: [# of milliseconds spent reading/writing][iostat doc]
|
||||
* `disk_bytes_used`: Disk usage in Bytes. The usage state is reported under the `state` metric label (e.g. `used`, `free`). Summing values of all states yields the disk size.
|
||||
|
||||
By setting the `metricsConfigs` field and `displayName` field ([example](https://github.com/kubernetes/node-problem-detector/blob/master/config/system-stats-monitor.json)), you can specify the list of metrics to be collected, and their display names on the Prometheus scaping endpoint. The name of the disk block device will be reported in the `device` metrics label.
|
||||
The name of the disk block device is reported in the `device_name` metric label (e.g. `sda`).
|
||||
|
||||
For the metrics that separates read/write operations, the IO direction is reported in the `direction` metric label (e.g. `read`, `write`).
|
||||
|
||||
And a few other options:
|
||||
* `includeRootBlk`: When set to `true`, add all block devices that's [not a slave or holder device](http://man7.org/linux/man-pages/man8/lsblk.8.html) to the list of disks that System Stats Monitor collects metrics from. When set to `false`, do not modify the list of disks that System Stats Monitor collects metrics from.
|
||||
* `includeRootBlk`: When set to `true`, add all block devices that's [not a slave or holder device][lsblk doc] to the list of disks that System Stats Monitor collects metrics from. When set to `false`, do not modify the list of disks that System Stats Monitor collects metrics from.
|
||||
* `includeAllAttachedBlk`: When set to `true`, add all currently attached block devices to the list of disks that System Stats Monitor collects metrics from. When set to `false`, do not modify the list of disks that System Stats Monitor collects metrics from.
|
||||
* `lsblkTimeout`: System Stats Monitor uses [`lsblk`](http://man7.org/linux/man-pages/man8/lsblk.8.html) to retrieve block devices information. This option sets the timeout for calling `lsblk` commands.
|
||||
* `lsblkTimeout`: System Stats Monitor uses [`lsblk`][lsblk doc] to retrieve block devices information. This option sets the timeout for calling `lsblk` commands.
|
||||
|
||||
[iostat doc]: https://www.kernel.org/doc/Documentation/iostats.txt
|
||||
[lsblk doc]: http://man7.org/linux/man-pages/man8/lsblk.8.html
|
||||
|
||||
### Host
|
||||
|
||||
Below metrics are collected from `host` component:
|
||||
|
||||
* `host_uptime`: The uptime of the operating system, in seconds. OS version and kernel versions are reported under the `os_version` and `kernel_version` metric label (e.g. `cos 73-11647.217.0`, `4.14.127+`).
|
||||
|
||||
### Memory
|
||||
|
||||
Below metrics are collected from `memory` component:
|
||||
|
||||
* `memory_bytes_used`: Memory usage by each memory state, in Bytes. The memory state is reported under the `state` metric label (e.g. `free`, `used`, `buffered`...). Summing values of all states yields the total memory of the node.
|
||||
* `memory_anonymous_used`: Anonymous memory usage, in Bytes. Memory usage state is reported under the `state` metric label (e.g. `active`, `inactive`). `active` means the memory has been used more recently and usually not swapped until needed. Summing values of all states yields the total anonymous memory used.
|
||||
* `memory_page_cache_used`: Page cache memory usage, in Bytes. Memory usage state is reported under the `state` metric label (e.g. `active`, `inactive`). `active` means the memory has been used more recently and usually not reclaimed until needed. Summing values of all states yields the total page cache memory used.
|
||||
* `memory_unevictable_used`: [Unevictable memory][/proc doc] usage, in Bytes.
|
||||
* `memory_dirty_used`: Dirty pages usage, in Bytes. Memory usage state is reported under the `state` metric label (e.g. `dirty`, `writeback`). `dirty` means the memory is waiting to be written back to disk, and `writeback` means the memory is actively being written back to disk.
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package systemstatsmonitor
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/load"
|
||||
|
||||
ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types"
|
||||
"k8s.io/node-problem-detector/pkg/util/metrics"
|
||||
)
|
||||
|
||||
// clockTick is the ratio between 1 second and 1 USER_HZ (a clock tick).
|
||||
//
|
||||
// CLK_TCK is 100 in most architectures. If NPD ever runs on a super special architecture,
|
||||
// we can work out a way to detect the clock tick on that architecture (might require
|
||||
// cross-compilation with C library or parsing kernel ABIs). For now, it's not worth the
|
||||
// complexity.
|
||||
//
|
||||
// See documentation at http://man7.org/linux/man-pages/man5/proc.5.html
|
||||
const clockTick float64 = 100.0
|
||||
|
||||
type cpuCollector struct {
|
||||
mRunnableTaskCount *metrics.Float64Metric
|
||||
mUsageTime *metrics.Float64Metric
|
||||
|
||||
config *ssmtypes.CPUStatsConfig
|
||||
|
||||
lastUsageTime map[string]float64
|
||||
}
|
||||
|
||||
func NewCPUCollectorOrDie(cpuConfig *ssmtypes.CPUStatsConfig) *cpuCollector {
|
||||
cc := cpuCollector{config: cpuConfig}
|
||||
|
||||
var err error
|
||||
|
||||
cc.mRunnableTaskCount, err = metrics.NewFloat64Metric(
|
||||
metrics.CPURunnableTaskCountID,
|
||||
cpuConfig.MetricsConfigs[string(metrics.CPURunnableTaskCountID)].DisplayName,
|
||||
"The average number of runnable tasks in the run-queue during the last minute",
|
||||
"1",
|
||||
metrics.LastValue,
|
||||
[]string{})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.CPURunnableTaskCountID, err)
|
||||
}
|
||||
|
||||
cc.mUsageTime, err = metrics.NewFloat64Metric(
|
||||
metrics.CPUUsageTimeID,
|
||||
cpuConfig.MetricsConfigs[string(metrics.CPUUsageTimeID)].DisplayName,
|
||||
"CPU usage, in seconds",
|
||||
"s",
|
||||
metrics.Sum,
|
||||
[]string{stateLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.CPUUsageTimeID, err)
|
||||
}
|
||||
|
||||
cc.lastUsageTime = make(map[string]float64)
|
||||
|
||||
return &cc
|
||||
}
|
||||
|
||||
func (cc *cpuCollector) recordLoad() {
|
||||
if cc.mRunnableTaskCount == nil {
|
||||
return
|
||||
}
|
||||
|
||||
loadAvg, err := load.Avg()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to retrieve average CPU load: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cc.mRunnableTaskCount.Record(map[string]string{}, loadAvg.Load1)
|
||||
}
|
||||
|
||||
func (cc *cpuCollector) recordUsage() {
|
||||
if cc.mUsageTime == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set percpu=false to get aggregated usage from all CPUs.
|
||||
timersStats, err := cpu.Times(false)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to retrieve CPU timers stat: %v", err)
|
||||
return
|
||||
}
|
||||
timersStat := timersStats[0]
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "user"}, clockTick*timersStat.User-cc.lastUsageTime["user"])
|
||||
cc.lastUsageTime["user"] = clockTick * timersStat.User
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "system"}, clockTick*timersStat.System-cc.lastUsageTime["system"])
|
||||
cc.lastUsageTime["system"] = clockTick * timersStat.System
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "idle"}, clockTick*timersStat.Idle-cc.lastUsageTime["idle"])
|
||||
cc.lastUsageTime["idle"] = clockTick * timersStat.Idle
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "nice"}, clockTick*timersStat.Nice-cc.lastUsageTime["nice"])
|
||||
cc.lastUsageTime["nice"] = clockTick * timersStat.Nice
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "iowait"}, clockTick*timersStat.Iowait-cc.lastUsageTime["iowait"])
|
||||
cc.lastUsageTime["iowait"] = clockTick * timersStat.Iowait
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "irq"}, clockTick*timersStat.Irq-cc.lastUsageTime["irq"])
|
||||
cc.lastUsageTime["irq"] = clockTick * timersStat.Irq
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "softirq"}, clockTick*timersStat.Softirq-cc.lastUsageTime["softirq"])
|
||||
cc.lastUsageTime["softirq"] = clockTick * timersStat.Softirq
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "steal"}, clockTick*timersStat.Steal-cc.lastUsageTime["steal"])
|
||||
cc.lastUsageTime["steal"] = clockTick * timersStat.Steal
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "guest"}, clockTick*timersStat.Guest-cc.lastUsageTime["guest"])
|
||||
cc.lastUsageTime["guest"] = clockTick * timersStat.Guest
|
||||
|
||||
cc.mUsageTime.Record(map[string]string{stateLabel: "guest_nice"}, clockTick*timersStat.GuestNice-cc.lastUsageTime["guest_nice"])
|
||||
cc.lastUsageTime["guest_nice"] = clockTick * timersStat.GuestNice
|
||||
}
|
||||
|
||||
func (cc *cpuCollector) collect() {
|
||||
if cc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cc.recordLoad()
|
||||
cc.recordUsage()
|
||||
}
|
||||
@@ -29,18 +29,30 @@ import (
|
||||
"k8s.io/node-problem-detector/pkg/util/metrics"
|
||||
)
|
||||
|
||||
const deviceNameLabel = "device_name"
|
||||
|
||||
type diskCollector struct {
|
||||
mIOTime *metrics.Int64Metric
|
||||
mWeightedIO *metrics.Int64Metric
|
||||
mAvgQueueLen *metrics.Float64Metric
|
||||
mIOTime *metrics.Int64Metric
|
||||
mWeightedIO *metrics.Int64Metric
|
||||
mAvgQueueLen *metrics.Float64Metric
|
||||
mOpsCount *metrics.Int64Metric
|
||||
mMergedOpsCount *metrics.Int64Metric
|
||||
mOpsBytes *metrics.Int64Metric
|
||||
mOpsTime *metrics.Int64Metric
|
||||
mBytesUsed *metrics.Int64Metric
|
||||
|
||||
config *ssmtypes.DiskStatsConfig
|
||||
|
||||
historyIOTime map[string]uint64
|
||||
historyWeightedIO map[string]uint64
|
||||
lastSampleTime time.Time
|
||||
lastIOTime map[string]uint64
|
||||
lastWeightedIO map[string]uint64
|
||||
lastReadCount map[string]uint64
|
||||
lastWriteCount map[string]uint64
|
||||
lastMergedReadCount map[string]uint64
|
||||
lastMergedWriteCount map[string]uint64
|
||||
lastReadBytes map[string]uint64
|
||||
lastWriteBytes map[string]uint64
|
||||
lastReadTime map[string]uint64
|
||||
lastWriteTime map[string]uint64
|
||||
|
||||
lastSampleTime time.Time
|
||||
}
|
||||
|
||||
func NewDiskCollectorOrDie(diskConfig *ssmtypes.DiskStatsConfig) *diskCollector {
|
||||
@@ -52,7 +64,7 @@ func NewDiskCollectorOrDie(diskConfig *ssmtypes.DiskStatsConfig) *diskCollector
|
||||
dc.mIOTime, err = metrics.NewInt64Metric(
|
||||
metrics.DiskIOTimeID,
|
||||
diskConfig.MetricsConfigs[string(metrics.DiskIOTimeID)].DisplayName,
|
||||
"The IO time spent on the disk",
|
||||
"The IO time spent on the disk, in ms",
|
||||
"ms",
|
||||
metrics.Sum,
|
||||
[]string{deviceNameLabel})
|
||||
@@ -64,7 +76,7 @@ func NewDiskCollectorOrDie(diskConfig *ssmtypes.DiskStatsConfig) *diskCollector
|
||||
dc.mWeightedIO, err = metrics.NewInt64Metric(
|
||||
metrics.DiskWeightedIOID,
|
||||
diskConfig.MetricsConfigs[string(metrics.DiskWeightedIOID)].DisplayName,
|
||||
"The weighted IO on the disk",
|
||||
"The weighted IO on the disk, in ms",
|
||||
"ms",
|
||||
metrics.Sum,
|
||||
[]string{deviceNameLabel})
|
||||
@@ -83,45 +95,86 @@ func NewDiskCollectorOrDie(diskConfig *ssmtypes.DiskStatsConfig) *diskCollector
|
||||
glog.Fatalf("Error initializing metric for disk/avg_queue_len: %v", err)
|
||||
}
|
||||
|
||||
dc.historyIOTime = make(map[string]uint64)
|
||||
dc.historyWeightedIO = make(map[string]uint64)
|
||||
dc.mOpsCount, err = metrics.NewInt64Metric(
|
||||
metrics.DiskOpsCountID,
|
||||
diskConfig.MetricsConfigs[string(metrics.DiskOpsCountID)].DisplayName,
|
||||
"Disk operations count",
|
||||
"1",
|
||||
metrics.Sum,
|
||||
[]string{deviceNameLabel, directionLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.DiskOpsCountID, err)
|
||||
}
|
||||
|
||||
dc.mMergedOpsCount, err = metrics.NewInt64Metric(
|
||||
metrics.DiskMergedOpsCountID,
|
||||
diskConfig.MetricsConfigs[string(metrics.DiskMergedOpsCountID)].DisplayName,
|
||||
"Disk merged operations count",
|
||||
"1",
|
||||
metrics.Sum,
|
||||
[]string{deviceNameLabel, directionLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.DiskMergedOpsCountID, err)
|
||||
}
|
||||
|
||||
dc.mOpsBytes, err = metrics.NewInt64Metric(
|
||||
metrics.DiskOpsBytesID,
|
||||
diskConfig.MetricsConfigs[string(metrics.DiskOpsBytesID)].DisplayName,
|
||||
"Bytes transferred in disk operations",
|
||||
"1",
|
||||
metrics.Sum,
|
||||
[]string{deviceNameLabel, directionLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.DiskOpsBytesID, err)
|
||||
}
|
||||
|
||||
dc.mOpsTime, err = metrics.NewInt64Metric(
|
||||
metrics.DiskOpsTimeID,
|
||||
diskConfig.MetricsConfigs[string(metrics.DiskOpsTimeID)].DisplayName,
|
||||
"Time spent in disk operations, in ms",
|
||||
"ms",
|
||||
metrics.Sum,
|
||||
[]string{deviceNameLabel, directionLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.DiskOpsTimeID, err)
|
||||
}
|
||||
|
||||
dc.mBytesUsed, err = metrics.NewInt64Metric(
|
||||
metrics.DiskBytesUsedID,
|
||||
diskConfig.MetricsConfigs[string(metrics.DiskBytesUsedID)].DisplayName,
|
||||
"Disk bytes used, in Bytes",
|
||||
"Byte",
|
||||
metrics.LastValue,
|
||||
[]string{deviceNameLabel, stateLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.DiskBytesUsedID, err)
|
||||
}
|
||||
|
||||
dc.lastIOTime = make(map[string]uint64)
|
||||
dc.lastWeightedIO = make(map[string]uint64)
|
||||
dc.lastReadCount = make(map[string]uint64)
|
||||
dc.lastWriteCount = make(map[string]uint64)
|
||||
dc.lastMergedReadCount = make(map[string]uint64)
|
||||
dc.lastMergedWriteCount = make(map[string]uint64)
|
||||
dc.lastReadBytes = make(map[string]uint64)
|
||||
dc.lastWriteBytes = make(map[string]uint64)
|
||||
dc.lastReadTime = make(map[string]uint64)
|
||||
dc.lastWriteTime = make(map[string]uint64)
|
||||
|
||||
return &dc
|
||||
}
|
||||
|
||||
func (dc *diskCollector) collect() {
|
||||
if dc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
blks := []string{}
|
||||
if dc.config.IncludeRootBlk {
|
||||
blks = append(blks, listRootBlockDevices(dc.config.LsblkTimeout)...)
|
||||
}
|
||||
if dc.config.IncludeAllAttachedBlk {
|
||||
blks = append(blks, listAttachedBlockDevices()...)
|
||||
}
|
||||
|
||||
ioCountersStats, err := disk.IOCounters(blks...)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to retrieve disk IO counters: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
sampleTime := time.Now()
|
||||
|
||||
func (dc *diskCollector) recordIOCounters(ioCountersStats map[string]disk.IOCountersStat, sampleTime time.Time) {
|
||||
for deviceName, ioCountersStat := range ioCountersStats {
|
||||
// Calculate average IO queue length since last measurement.
|
||||
lastIOTime, historyExist := dc.historyIOTime[deviceName]
|
||||
lastWeightedIO := dc.historyWeightedIO[deviceName]
|
||||
lastSampleTime := dc.lastSampleTime
|
||||
|
||||
dc.historyIOTime[deviceName] = ioCountersStat.IoTime
|
||||
dc.historyWeightedIO[deviceName] = ioCountersStat.WeightedIO
|
||||
dc.lastSampleTime = sampleTime
|
||||
|
||||
// Attach label {"device_name": deviceName} to the metrics.
|
||||
// Attach label {"device_name": deviceName} to the following metrics.
|
||||
tags := map[string]string{deviceNameLabel: deviceName}
|
||||
|
||||
// Calculate average IO queue length since last measurement.
|
||||
lastIOTime, historyExist := dc.lastIOTime[deviceName]
|
||||
lastWeightedIO := dc.lastWeightedIO[deviceName]
|
||||
dc.lastIOTime[deviceName] = ioCountersStat.IoTime
|
||||
dc.lastWeightedIO[deviceName] = ioCountersStat.WeightedIO
|
||||
|
||||
if dc.mIOTime != nil {
|
||||
dc.mIOTime.Record(tags, int64(ioCountersStat.IoTime-lastIOTime))
|
||||
}
|
||||
@@ -131,16 +184,104 @@ func (dc *diskCollector) collect() {
|
||||
if historyExist {
|
||||
avgQueueLen := float64(0.0)
|
||||
if lastWeightedIO != ioCountersStat.WeightedIO {
|
||||
diffSampleTimeMs := sampleTime.Sub(lastSampleTime).Seconds() * 1000
|
||||
diffSampleTimeMs := sampleTime.Sub(dc.lastSampleTime).Seconds() * 1000
|
||||
avgQueueLen = float64(ioCountersStat.WeightedIO-lastWeightedIO) / diffSampleTimeMs
|
||||
}
|
||||
if dc.mAvgQueueLen != nil {
|
||||
dc.mAvgQueueLen.Record(tags, avgQueueLen)
|
||||
}
|
||||
}
|
||||
|
||||
// Attach label {"device_name": deviceName, "direction": "read"} to the following metrics.
|
||||
tags = map[string]string{deviceNameLabel: deviceName, directionLabel: "read"}
|
||||
|
||||
if dc.mOpsCount != nil {
|
||||
dc.mOpsCount.Record(tags, int64(ioCountersStat.ReadCount-dc.lastReadCount[deviceName]))
|
||||
dc.lastReadCount[deviceName] = ioCountersStat.ReadCount
|
||||
}
|
||||
if dc.mMergedOpsCount != nil {
|
||||
dc.mMergedOpsCount.Record(tags, int64(ioCountersStat.MergedReadCount-dc.lastMergedReadCount[deviceName]))
|
||||
dc.lastMergedReadCount[deviceName] = ioCountersStat.MergedReadCount
|
||||
}
|
||||
if dc.mOpsBytes != nil {
|
||||
dc.mOpsBytes.Record(tags, int64(ioCountersStat.ReadBytes-dc.lastReadBytes[deviceName]))
|
||||
dc.lastReadBytes[deviceName] = ioCountersStat.ReadBytes
|
||||
}
|
||||
if dc.mOpsTime != nil {
|
||||
dc.mOpsTime.Record(tags, int64(ioCountersStat.ReadTime-dc.lastReadTime[deviceName]))
|
||||
dc.lastReadTime[deviceName] = ioCountersStat.ReadTime
|
||||
}
|
||||
|
||||
// Attach label {"device_name": deviceName, "direction": "write"} to the following metrics.
|
||||
tags = map[string]string{deviceNameLabel: deviceName, directionLabel: "write"}
|
||||
|
||||
if dc.mOpsCount != nil {
|
||||
dc.mOpsCount.Record(tags, int64(ioCountersStat.WriteCount-dc.lastWriteCount[deviceName]))
|
||||
dc.lastWriteCount[deviceName] = ioCountersStat.WriteCount
|
||||
}
|
||||
if dc.mMergedOpsCount != nil {
|
||||
dc.mMergedOpsCount.Record(tags, int64(ioCountersStat.MergedWriteCount-dc.lastMergedWriteCount[deviceName]))
|
||||
dc.lastMergedWriteCount[deviceName] = ioCountersStat.MergedWriteCount
|
||||
}
|
||||
if dc.mOpsBytes != nil {
|
||||
dc.mOpsBytes.Record(tags, int64(ioCountersStat.WriteBytes-dc.lastWriteBytes[deviceName]))
|
||||
dc.lastWriteBytes[deviceName] = ioCountersStat.WriteBytes
|
||||
}
|
||||
if dc.mOpsTime != nil {
|
||||
dc.mOpsTime.Record(tags, int64(ioCountersStat.WriteTime-dc.lastWriteTime[deviceName]))
|
||||
dc.lastWriteTime[deviceName] = ioCountersStat.WriteTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *diskCollector) collect() {
|
||||
if dc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// List available devices.
|
||||
devices := []string{}
|
||||
if dc.config.IncludeRootBlk {
|
||||
devices = append(devices, listRootBlockDevices(dc.config.LsblkTimeout)...)
|
||||
}
|
||||
if dc.config.IncludeAllAttachedBlk {
|
||||
devices = append(devices, listAttachedBlockDevices()...)
|
||||
}
|
||||
|
||||
// Fetch metrics from /proc, /sys.
|
||||
ioCountersStats, err := disk.IOCounters(devices...)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to retrieve disk IO counters: %v", err)
|
||||
return
|
||||
}
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to list disk partitions: %v", err)
|
||||
return
|
||||
}
|
||||
sampleTime := time.Now()
|
||||
defer func() { dc.lastSampleTime = sampleTime }()
|
||||
|
||||
// Record metrics regarding disk IO.
|
||||
dc.recordIOCounters(ioCountersStats, sampleTime)
|
||||
|
||||
// Record metrics regarding disk space usage.
|
||||
if dc.mBytesUsed == nil {
|
||||
return
|
||||
}
|
||||
for _, partition := range partitions {
|
||||
usageStat, err := disk.Usage(partition.Mountpoint)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to retrieve disk usage for %q: %v", partition.Mountpoint, err)
|
||||
continue
|
||||
}
|
||||
deviceName := strings.TrimPrefix(partition.Device, "/dev/")
|
||||
dc.mBytesUsed.Record(map[string]string{deviceNameLabel: deviceName, stateLabel: "free"}, int64(usageStat.Free))
|
||||
dc.mBytesUsed.Record(map[string]string{deviceNameLabel: deviceName, stateLabel: "used"}, int64(usageStat.Used))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// listRootBlockDevices lists all block devices that's not a slave or holder.
|
||||
func listRootBlockDevices(timeout time.Duration) []string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package systemstatsmonitor
|
||||
|
||||
// deviceNameLabel labels the monitored disk device, e.g.: "sda", "sda1".
|
||||
const deviceNameLabel = "device_name"
|
||||
|
||||
// directionLabel labels the direction of the disk operations, e.g.: "read", "write".
|
||||
const directionLabel = "direction"
|
||||
|
||||
// stateLabel labels the state of disk/memory/cpu usage, e.g.: "free", "used".
|
||||
const stateLabel = "state"
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package systemstatsmonitor
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/prometheus/procfs"
|
||||
|
||||
ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types"
|
||||
"k8s.io/node-problem-detector/pkg/util/metrics"
|
||||
)
|
||||
|
||||
type memoryCollector struct {
|
||||
mBytesUsed *metrics.Int64Metric
|
||||
mAnonymousUsed *metrics.Int64Metric
|
||||
mPageCacheUsed *metrics.Int64Metric
|
||||
mUnevictableUsed *metrics.Int64Metric
|
||||
mDirtyUsed *metrics.Int64Metric
|
||||
|
||||
config *ssmtypes.MemoryStatsConfig
|
||||
}
|
||||
|
||||
func NewMemoryCollectorOrDie(memoryConfig *ssmtypes.MemoryStatsConfig) *memoryCollector {
|
||||
mc := memoryCollector{config: memoryConfig}
|
||||
|
||||
var err error
|
||||
|
||||
mc.mBytesUsed, err = metrics.NewInt64Metric(
|
||||
metrics.MemoryBytesUsedID,
|
||||
memoryConfig.MetricsConfigs[string(metrics.MemoryBytesUsedID)].DisplayName,
|
||||
"Memory usage by each memory state, in Bytes. Summing values of all states yields the total memory on the node.",
|
||||
"Byte",
|
||||
metrics.LastValue,
|
||||
[]string{stateLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.MemoryBytesUsedID, err)
|
||||
}
|
||||
|
||||
mc.mAnonymousUsed, err = metrics.NewInt64Metric(
|
||||
metrics.MemoryAnonymousUsedID,
|
||||
memoryConfig.MetricsConfigs[string(metrics.MemoryAnonymousUsedID)].DisplayName,
|
||||
"Anonymous memory usage, in Bytes. Summing values of all states yields the total anonymous memory used.",
|
||||
"Byte",
|
||||
metrics.LastValue,
|
||||
[]string{stateLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.MemoryAnonymousUsedID, err)
|
||||
}
|
||||
|
||||
mc.mPageCacheUsed, err = metrics.NewInt64Metric(
|
||||
metrics.MemoryPageCacheUsedID,
|
||||
memoryConfig.MetricsConfigs[string(metrics.MemoryPageCacheUsedID)].DisplayName,
|
||||
"Page cache memory usage, in Bytes. Summing values of all states yields the total anonymous memory used.",
|
||||
"Byte",
|
||||
metrics.LastValue,
|
||||
[]string{stateLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.MemoryPageCacheUsedID, err)
|
||||
}
|
||||
|
||||
mc.mUnevictableUsed, err = metrics.NewInt64Metric(
|
||||
metrics.MemoryUnevictableUsedID,
|
||||
memoryConfig.MetricsConfigs[string(metrics.MemoryUnevictableUsedID)].DisplayName,
|
||||
"Unevictable memory usage, in Bytes",
|
||||
"Byte",
|
||||
metrics.LastValue,
|
||||
[]string{})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.MemoryUnevictableUsedID, err)
|
||||
}
|
||||
|
||||
mc.mDirtyUsed, err = metrics.NewInt64Metric(
|
||||
metrics.MemoryDirtyUsedID,
|
||||
memoryConfig.MetricsConfigs[string(metrics.MemoryDirtyUsedID)].DisplayName,
|
||||
"Dirty pages usage, in Bytes. Dirty means the memory is waiting to be written back to disk, and writeback means the memory is actively being written back to disk.",
|
||||
"Byte",
|
||||
metrics.LastValue,
|
||||
[]string{stateLabel})
|
||||
if err != nil {
|
||||
glog.Fatalf("Error initializing metric for %q: %v", metrics.MemoryDirtyUsedID, err)
|
||||
}
|
||||
|
||||
return &mc
|
||||
}
|
||||
|
||||
func (mc *memoryCollector) collect() {
|
||||
if mc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
proc, err := procfs.NewDefaultFS()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to find /proc mount point: %v", err)
|
||||
return
|
||||
}
|
||||
meminfo, err := proc.Meminfo()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to retrieve memory stats: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if mc.mBytesUsed != nil {
|
||||
memUsed := meminfo.MemTotal - meminfo.MemFree - meminfo.Buffers - meminfo.Cached - meminfo.Slab
|
||||
mc.mBytesUsed.Record(map[string]string{stateLabel: "free"}, int64(meminfo.MemFree))
|
||||
mc.mBytesUsed.Record(map[string]string{stateLabel: "used"}, int64(memUsed))
|
||||
mc.mBytesUsed.Record(map[string]string{stateLabel: "buffered"}, int64(meminfo.Buffers))
|
||||
mc.mBytesUsed.Record(map[string]string{stateLabel: "cached"}, int64(meminfo.Cached))
|
||||
mc.mBytesUsed.Record(map[string]string{stateLabel: "slab"}, int64(meminfo.Slab))
|
||||
}
|
||||
|
||||
if mc.mDirtyUsed != nil {
|
||||
mc.mDirtyUsed.Record(map[string]string{stateLabel: "dirty"}, int64(meminfo.Dirty))
|
||||
mc.mDirtyUsed.Record(map[string]string{stateLabel: "writeback"}, int64(meminfo.Writeback))
|
||||
}
|
||||
|
||||
if mc.mAnonymousUsed != nil {
|
||||
mc.mAnonymousUsed.Record(map[string]string{stateLabel: "active"}, int64(meminfo.ActiveAnon))
|
||||
mc.mAnonymousUsed.Record(map[string]string{stateLabel: "inactive"}, int64(meminfo.InactiveAnon))
|
||||
}
|
||||
|
||||
if mc.mPageCacheUsed != nil {
|
||||
mc.mPageCacheUsed.Record(map[string]string{stateLabel: "active"}, int64(meminfo.ActiveFile))
|
||||
mc.mPageCacheUsed.Record(map[string]string{stateLabel: "inactive"}, int64(meminfo.InactiveFile))
|
||||
}
|
||||
|
||||
if mc.mUnevictableUsed != nil {
|
||||
mc.mUnevictableUsed.Record(map[string]string{}, int64(meminfo.Unevictable))
|
||||
}
|
||||
}
|
||||
@@ -38,11 +38,13 @@ func init() {
|
||||
}
|
||||
|
||||
type systemStatsMonitor struct {
|
||||
configPath string
|
||||
config ssmtypes.SystemStatsConfig
|
||||
diskCollector *diskCollector
|
||||
hostCollector *hostCollector
|
||||
tomb *tomb.Tomb
|
||||
configPath string
|
||||
config ssmtypes.SystemStatsConfig
|
||||
cpuCollector *cpuCollector
|
||||
diskCollector *diskCollector
|
||||
hostCollector *hostCollector
|
||||
memoryCollector *memoryCollector
|
||||
tomb *tomb.Tomb
|
||||
}
|
||||
|
||||
// NewSystemStatsMonitorOrDie creates a system stats monitor.
|
||||
@@ -72,12 +74,18 @@ func NewSystemStatsMonitorOrDie(configPath string) types.Monitor {
|
||||
glog.Fatalf("Failed to validate %s configuration %+v: %v", ssm.configPath, ssm.config, err)
|
||||
}
|
||||
|
||||
if len(ssm.config.CPUConfig.MetricsConfigs) > 0 {
|
||||
ssm.cpuCollector = NewCPUCollectorOrDie(&ssm.config.CPUConfig)
|
||||
}
|
||||
if len(ssm.config.DiskConfig.MetricsConfigs) > 0 {
|
||||
ssm.diskCollector = NewDiskCollectorOrDie(&ssm.config.DiskConfig)
|
||||
}
|
||||
if len(ssm.config.HostConfig.MetricsConfigs) > 0 {
|
||||
ssm.hostCollector = NewHostCollectorOrDie(&ssm.config.HostConfig)
|
||||
}
|
||||
if len(ssm.config.MemoryConfig.MetricsConfigs) > 0 {
|
||||
ssm.memoryCollector = NewMemoryCollectorOrDie(&ssm.config.MemoryConfig)
|
||||
}
|
||||
return &ssm
|
||||
}
|
||||
|
||||
@@ -98,15 +106,19 @@ func (ssm *systemStatsMonitor) monitorLoop() {
|
||||
glog.Infof("System stats monitor stopped: %s", ssm.configPath)
|
||||
return
|
||||
default:
|
||||
ssm.cpuCollector.collect()
|
||||
ssm.diskCollector.collect()
|
||||
ssm.hostCollector.collect()
|
||||
ssm.memoryCollector.collect()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-runTicker.C:
|
||||
ssm.cpuCollector.collect()
|
||||
ssm.diskCollector.collect()
|
||||
ssm.hostCollector.collect()
|
||||
ssm.memoryCollector.collect()
|
||||
case <-ssm.tomb.Stopping():
|
||||
glog.Infof("System stats monitor stopped: %s", ssm.configPath)
|
||||
return
|
||||
|
||||
@@ -30,6 +30,10 @@ type MetricConfig struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
|
||||
type CPUStatsConfig struct {
|
||||
MetricsConfigs map[string]MetricConfig `json:"metricsConfigs"`
|
||||
}
|
||||
|
||||
type DiskStatsConfig struct {
|
||||
MetricsConfigs map[string]MetricConfig `json:"metricsConfigs"`
|
||||
IncludeRootBlk bool `json:"includeRootBlk"`
|
||||
@@ -42,11 +46,17 @@ type HostStatsConfig struct {
|
||||
MetricsConfigs map[string]MetricConfig `json:"metricsConfigs"`
|
||||
}
|
||||
|
||||
type MemoryStatsConfig struct {
|
||||
MetricsConfigs map[string]MetricConfig `json:"metricsConfigs"`
|
||||
}
|
||||
|
||||
type SystemStatsConfig struct {
|
||||
DiskConfig DiskStatsConfig `json:"disk"`
|
||||
HostConfig HostStatsConfig `json:"host"`
|
||||
InvokeIntervalString string `json:"invokeInterval"`
|
||||
InvokeInterval time.Duration `json:"-"`
|
||||
CPUConfig CPUStatsConfig `json:"cpu"`
|
||||
DiskConfig DiskStatsConfig `json:"disk"`
|
||||
HostConfig HostStatsConfig `json:"host"`
|
||||
MemoryConfig MemoryStatsConfig `json:"memory"`
|
||||
InvokeIntervalString string `json:"invokeInterval"`
|
||||
InvokeInterval time.Duration `json:"-"`
|
||||
}
|
||||
|
||||
// ApplyConfiguration applies default configurations.
|
||||
|
||||
@@ -20,12 +20,24 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ProblemCounterID MetricID = "problem_counter"
|
||||
ProblemGaugeID MetricID = "problem_gauge"
|
||||
DiskIOTimeID MetricID = "disk/io_time"
|
||||
DiskWeightedIOID MetricID = "disk/weighted_io"
|
||||
DiskAvgQueueLenID MetricID = "disk/avg_queue_len"
|
||||
HostUptimeID MetricID = "host/uptime"
|
||||
CPURunnableTaskCountID MetricID = "cpu/runnable_task_count"
|
||||
CPUUsageTimeID MetricID = "cpu/usage_time"
|
||||
ProblemCounterID MetricID = "problem_counter"
|
||||
ProblemGaugeID MetricID = "problem_gauge"
|
||||
DiskIOTimeID MetricID = "disk/io_time"
|
||||
DiskWeightedIOID MetricID = "disk/weighted_io"
|
||||
DiskAvgQueueLenID MetricID = "disk/avg_queue_len"
|
||||
DiskOpsCountID MetricID = "disk/operation_count"
|
||||
DiskMergedOpsCountID MetricID = "disk/merged_operation_count"
|
||||
DiskOpsBytesID MetricID = "disk/operation_bytes_count"
|
||||
DiskOpsTimeID MetricID = "disk/operation_time"
|
||||
DiskBytesUsedID MetricID = "disk/bytes_used"
|
||||
HostUptimeID MetricID = "host/uptime"
|
||||
MemoryBytesUsedID MetricID = "memory/bytes_used"
|
||||
MemoryAnonymousUsedID MetricID = "memory/anonymous_used"
|
||||
MemoryPageCacheUsedID MetricID = "memory/page_cache_used"
|
||||
MemoryUnevictableUsedID MetricID = "memory/unevictable_used"
|
||||
MemoryDirtyUsedID MetricID = "memory/dirty_used"
|
||||
)
|
||||
|
||||
var MetricMap MetricMapping
|
||||
|
||||
@@ -68,15 +68,28 @@ var _ = ginkgo.Describe("NPD should export Prometheus metrics.", func() {
|
||||
|
||||
ginkgo.Context("On a clean node", func() {
|
||||
|
||||
ginkgo.It("NPD should export host_uptime metric", func() {
|
||||
ginkgo.It("NPD should export cpu/disk/host/memory metric", func() {
|
||||
err := npd.WaitForNPD(instance, []string{"host_uptime"}, 120)
|
||||
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Expect NPD to become ready in 120s, but hit error: %v", err))
|
||||
|
||||
gotMetrics, err := npd.FetchNPDMetrics(instance)
|
||||
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error fetching NPD metrics: %v", err))
|
||||
|
||||
_, err = metrics.GetFloat64Metric(gotMetrics, "host_uptime", map[string]string{}, false)
|
||||
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to find uptime metric: %v.\nHere is all NPD exported metrics: %v", err, gotMetrics))
|
||||
assertMetricExist(gotMetrics, "cpu_runnable_task_count", map[string]string{}, true)
|
||||
assertMetricExist(gotMetrics, "cpu_usage_time", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "disk_operation_count", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "disk_merged_operation_count", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "disk_operation_bytes_count", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "disk_operation_time", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "disk_bytes_used", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "disk_io_time", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "disk_weighted_io", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "memory_bytes_used", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "memory_anonymous_used", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "memory_page_cache_used", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "memory_unevictable_used", map[string]string{}, true)
|
||||
assertMetricExist(gotMetrics, "memory_dirty_used", map[string]string{}, false)
|
||||
assertMetricExist(gotMetrics, "host_uptime", map[string]string{}, false)
|
||||
})
|
||||
|
||||
ginkgo.It("NPD should not report any problem", func() {
|
||||
@@ -180,6 +193,11 @@ var _ = ginkgo.Describe("NPD should export Prometheus metrics.", func() {
|
||||
})
|
||||
})
|
||||
|
||||
func assertMetricExist(metricList []metrics.Float64MetricRepresentation, metricName string, labels map[string]string, strictLabelMatching bool) {
|
||||
_, err := metrics.GetFloat64Metric(metricList, metricName, labels, strictLabelMatching)
|
||||
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to find metric %q: %v.\nHere is all NPD exported metrics: %v", metricName, err, metricList))
|
||||
}
|
||||
|
||||
func assertMetricValueInBound(instance gce.Instance, metricName string, labels map[string]string, lowBound float64, highBound float64) {
|
||||
value, err := npd.FetchNPDMetric(instance, metricName, labels)
|
||||
if err != nil {
|
||||
|
||||
-2
@@ -1,6 +1,4 @@
|
||||
# Run only staticcheck for now. Additional linters will be enabled one-by-one.
|
||||
linters:
|
||||
enable:
|
||||
- staticcheck
|
||||
- govet
|
||||
disable-all: true
|
||||
|
||||
+106
-3
@@ -2,17 +2,120 @@
|
||||
|
||||
Prometheus uses GitHub to manage reviews of pull requests.
|
||||
|
||||
* If you are a new contributor see: [Steps to Contribute](#steps-to-contribute)
|
||||
|
||||
* If you have a trivial fix or improvement, go ahead and create a pull request,
|
||||
addressing (with `@...`) the maintainer of this repository (see
|
||||
addressing (with `@...`) a suitable maintainer of this repository (see
|
||||
[MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request.
|
||||
|
||||
* If you plan to do something more involved, first discuss your ideas
|
||||
on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers).
|
||||
This will avoid unnecessary work and surely give you and us a good deal
|
||||
of inspiration.
|
||||
of inspiration. Also please see our [non-goals issue](https://github.com/prometheus/docs/issues/149) on areas that the Prometheus community doesn't plan to work on.
|
||||
|
||||
* Relevant coding style guidelines are the [Go Code Review
|
||||
Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments)
|
||||
and the _Formatting and style_ section of Peter Bourgon's [Go: Best
|
||||
Practices for Production
|
||||
Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style).
|
||||
Environments](https://peter.bourgon.org/go-in-production/#formatting-and-style).
|
||||
|
||||
* Be sure to sign off on the [DCO](https://github.com/probot/dco#how-it-works)
|
||||
|
||||
## Steps to Contribute
|
||||
|
||||
Should you wish to work on an issue, please claim it first by commenting on the GitHub issue that you want to work on it. This is to prevent duplicated efforts from contributors on the same issue.
|
||||
|
||||
Please check the [`help-wanted`](https://github.com/prometheus/procfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) label to find issues that are good for getting started. If you have questions about one of the issues, with or without the tag, please comment on them and one of the maintainers will clarify it. For a quicker response, contact us over [IRC](https://prometheus.io/community).
|
||||
|
||||
For quickly compiling and testing your changes do:
|
||||
```
|
||||
make test # Make sure all the tests pass before you commit and push :)
|
||||
```
|
||||
|
||||
We use [`golangci-lint`](https://github.com/golangci/golangci-lint) for linting the code. If it reports an issue and you think that the warning needs to be disregarded or is a false-positive, you can add a special comment `//nolint:linter1[,linter2,...]` before the offending line. Use this sparingly though, fixing the code to comply with the linter's recommendation is in general the preferred course of action.
|
||||
|
||||
## Pull Request Checklist
|
||||
|
||||
* Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. If it doesn't merge cleanly with master you may be asked to rebase your changes.
|
||||
|
||||
* Commits should be as small as possible, while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests).
|
||||
|
||||
* If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a comment, or you can ask for a review on IRC channel [#prometheus](https://webchat.freenode.net/?channels=#prometheus) on irc.freenode.net (for the easiest start, [join via Riot](https://riot.im/app/#/room/#prometheus:matrix.org)).
|
||||
|
||||
* Add tests relevant to the fixed bug or new feature.
|
||||
|
||||
## Dependency management
|
||||
|
||||
The Prometheus project uses [Go modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) to manage dependencies on external packages. This requires a working Go environment with version 1.12 or greater installed.
|
||||
|
||||
All dependencies are vendored in the `vendor/` directory.
|
||||
|
||||
To add or update a new dependency, use the `go get` command:
|
||||
|
||||
```bash
|
||||
# Pick the latest tagged release.
|
||||
go get example.com/some/module/pkg
|
||||
|
||||
# Pick a specific version.
|
||||
go get example.com/some/module/pkg@vX.Y.Z
|
||||
```
|
||||
|
||||
Tidy up the `go.mod` and `go.sum` files and copy the new/updated dependency to the `vendor/` directory:
|
||||
|
||||
|
||||
```bash
|
||||
# The GO111MODULE variable can be omitted when the code isn't located in GOPATH.
|
||||
GO111MODULE=on go mod tidy
|
||||
|
||||
GO111MODULE=on go mod vendor
|
||||
```
|
||||
|
||||
You have to commit the changes to `go.mod`, `go.sum` and the `vendor/` directory before submitting the pull request.
|
||||
|
||||
|
||||
## API Implementation Guidelines
|
||||
|
||||
### Naming and Documentation
|
||||
|
||||
Public functions and structs should normally be named according to the file(s) being read and parsed. For example,
|
||||
the `fs.BuddyInfo()` function reads the file `/proc/buddyinfo`. In addition, the godoc for each public function
|
||||
should contain the path to the file(s) being read and a URL of the linux kernel documentation describing the file(s).
|
||||
|
||||
### Reading vs. Parsing
|
||||
|
||||
Most functionality in this library consists of reading files and then parsing the text into structured data. In most
|
||||
cases reading and parsing should be separated into different functions/methods with a public `fs.Thing()` method and
|
||||
a private `parseThing(r Reader)` function. This provides a logical separation and allows parsing to be tested
|
||||
directly without the need to read from the filesystem. Using a `Reader` argument is preferred over other data types
|
||||
such as `string` or `*File` because it provides the most flexibility regarding the data source. When a set of files
|
||||
in a directory needs to be parsed, then a `path` string parameter to the parse function can be used instead.
|
||||
|
||||
### /proc and /sys filesystem I/O
|
||||
|
||||
The `proc` and `sys` filesystems are pseudo file systems and work a bit differently from standard disk I/O.
|
||||
Many of the files are changing continuously and the data being read can in some cases change between subsequent
|
||||
reads in the same file. Also, most of the files are relatively small (less than a few KBs), and system calls
|
||||
to the `stat` function will often return the wrong size. Therefore, for most files it's recommended to read the
|
||||
full file in a single operation using an internal utility function called `util.ReadFileNoStat`.
|
||||
This function is similar to `ioutil.ReadFile`, but it avoids the system call to `stat` to get the current size of
|
||||
the file.
|
||||
|
||||
Note that parsing the file's contents can still be performed one line at a time. This is done by first reading
|
||||
the full file, and then using a scanner on the `[]byte` or `string` containing the data.
|
||||
|
||||
```
|
||||
data, err := util.ReadFileNoStat("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader := bytes.NewReader(data)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
```
|
||||
|
||||
The `/sys` filesystem contains many very small files which contain only a single numeric or text value. These files
|
||||
can be read using an internal function called `util.SysReadFile` which is similar to `ioutil.ReadFile` but does
|
||||
not bother to check the size of the file before reading.
|
||||
```
|
||||
data, err := util.SysReadFile("/sys/class/power_supply/BAT0/capacity")
|
||||
```
|
||||
|
||||
|
||||
+12
-4
@@ -1,6 +1,6 @@
|
||||
# procfs
|
||||
|
||||
This procfs package provides functions to retrieve system, kernel and process
|
||||
This package provides functions to retrieve system, kernel, and process
|
||||
metrics from the pseudo-filesystems /proc and /sys.
|
||||
|
||||
*WARNING*: This package is a work in progress. Its API may still break in
|
||||
@@ -13,7 +13,8 @@ backwards-incompatible ways without warnings. Use it at your own risk.
|
||||
## Usage
|
||||
|
||||
The procfs library is organized by packages based on whether the gathered data is coming from
|
||||
/proc, /sys, or both. Each package contains an `FS` type which represents the path to either /proc, /sys, or both. For example, current cpu statistics are gathered from
|
||||
/proc, /sys, or both. Each package contains an `FS` type which represents the path to either /proc,
|
||||
/sys, or both. For example, cpu statistics are gathered from
|
||||
`/proc/stat` and are available via the root procfs package. First, the proc filesystem mount
|
||||
point is initialized, and then the stat information is read.
|
||||
|
||||
@@ -29,10 +30,17 @@ Some sub-packages such as `blockdevice`, require access to both the proc and sys
|
||||
stats, err := fs.ProcDiskstats()
|
||||
```
|
||||
|
||||
## Package Organization
|
||||
|
||||
The packages in this project are organized according to (1) whether the data comes from the `/proc` or
|
||||
`/sys` filesystem and (2) the type of information being retrieved. For example, most process information
|
||||
can be gathered from the functions in the root `procfs` package. Information about block devices such as disk drives
|
||||
is available in the `blockdevices` sub-package.
|
||||
|
||||
## Building and Testing
|
||||
|
||||
The procfs library is normally built as part of another application. However, when making
|
||||
changes to the library, the `make test` command can be used to run the API test suite.
|
||||
The procfs library is intended to be built as part of another application, so there are no distributable binaries.
|
||||
However, most of the API includes unit tests which can be run with `make test`.
|
||||
|
||||
### Updating Test Fixtures
|
||||
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ type BuddyInfo struct {
|
||||
Sizes []float64
|
||||
}
|
||||
|
||||
// NewBuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem.
|
||||
// BuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem.
|
||||
func (fs FS) BuddyInfo() ([]BuddyInfo, error) {
|
||||
file, err := os.Open(fs.proc.Path("buddyinfo"))
|
||||
if err != nil {
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// CPUInfo contains general information about a system CPU found in /proc/cpuinfo
|
||||
type CPUInfo struct {
|
||||
Processor uint
|
||||
VendorID string
|
||||
CPUFamily string
|
||||
Model string
|
||||
ModelName string
|
||||
Stepping string
|
||||
Microcode string
|
||||
CPUMHz float64
|
||||
CacheSize string
|
||||
PhysicalID string
|
||||
Siblings uint
|
||||
CoreID string
|
||||
CPUCores uint
|
||||
APICID string
|
||||
InitialAPICID string
|
||||
FPU string
|
||||
FPUException string
|
||||
CPUIDLevel uint
|
||||
WP string
|
||||
Flags []string
|
||||
Bugs []string
|
||||
BogoMips float64
|
||||
CLFlushSize uint
|
||||
CacheAlignment uint
|
||||
AddressSizes string
|
||||
PowerManagement string
|
||||
}
|
||||
|
||||
// CPUInfo returns information about current system CPUs.
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
func (fs FS) CPUInfo() ([]CPUInfo, error) {
|
||||
data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseCPUInfo(data)
|
||||
}
|
||||
|
||||
// parseCPUInfo parses data from /proc/cpuinfo
|
||||
func parseCPUInfo(info []byte) ([]CPUInfo, error) {
|
||||
cpuinfo := []CPUInfo{}
|
||||
i := -1
|
||||
scanner := bufio.NewScanner(bytes.NewReader(info))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
field := strings.SplitN(line, ": ", 2)
|
||||
switch strings.TrimSpace(field[0]) {
|
||||
case "processor":
|
||||
cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
|
||||
i++
|
||||
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].Processor = uint(v)
|
||||
case "vendor_id":
|
||||
cpuinfo[i].VendorID = field[1]
|
||||
case "cpu family":
|
||||
cpuinfo[i].CPUFamily = field[1]
|
||||
case "model":
|
||||
cpuinfo[i].Model = field[1]
|
||||
case "model name":
|
||||
cpuinfo[i].ModelName = field[1]
|
||||
case "stepping":
|
||||
cpuinfo[i].Stepping = field[1]
|
||||
case "microcode":
|
||||
cpuinfo[i].Microcode = field[1]
|
||||
case "cpu MHz":
|
||||
v, err := strconv.ParseFloat(field[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].CPUMHz = v
|
||||
case "cache size":
|
||||
cpuinfo[i].CacheSize = field[1]
|
||||
case "physical id":
|
||||
cpuinfo[i].PhysicalID = field[1]
|
||||
case "siblings":
|
||||
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].Siblings = uint(v)
|
||||
case "core id":
|
||||
cpuinfo[i].CoreID = field[1]
|
||||
case "cpu cores":
|
||||
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].CPUCores = uint(v)
|
||||
case "apicid":
|
||||
cpuinfo[i].APICID = field[1]
|
||||
case "initial apicid":
|
||||
cpuinfo[i].InitialAPICID = field[1]
|
||||
case "fpu":
|
||||
cpuinfo[i].FPU = field[1]
|
||||
case "fpu_exception":
|
||||
cpuinfo[i].FPUException = field[1]
|
||||
case "cpuid level":
|
||||
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].CPUIDLevel = uint(v)
|
||||
case "wp":
|
||||
cpuinfo[i].WP = field[1]
|
||||
case "flags":
|
||||
cpuinfo[i].Flags = strings.Fields(field[1])
|
||||
case "bugs":
|
||||
cpuinfo[i].Bugs = strings.Fields(field[1])
|
||||
case "bogomips":
|
||||
v, err := strconv.ParseFloat(field[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].BogoMips = v
|
||||
case "clflush size":
|
||||
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].CLFlushSize = uint(v)
|
||||
case "cache_alignment":
|
||||
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpuinfo[i].CacheAlignment = uint(v)
|
||||
case "address sizes":
|
||||
cpuinfo[i].AddressSizes = field[1]
|
||||
case "power management":
|
||||
cpuinfo[i].PowerManagement = field[1]
|
||||
}
|
||||
}
|
||||
return cpuinfo, nil
|
||||
|
||||
}
|
||||
+1523
-126
File diff suppressed because it is too large
Load Diff
+4
-2
@@ -1,6 +1,8 @@
|
||||
module github.com/prometheus/procfs
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.3.0
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
|
||||
github.com/google/go-cmp v0.3.1
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
)
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ const (
|
||||
// DefaultSysMountPoint is the common mount point of the sys filesystem.
|
||||
DefaultSysMountPoint = "/sys"
|
||||
|
||||
// DefaultConfigfsMountPoint is the commont mount point of the configfs
|
||||
// DefaultConfigfsMountPoint is the common mount point of the configfs
|
||||
DefaultConfigfsMountPoint = "/sys/kernel/config"
|
||||
)
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ReadFileNoStat uses ioutil.ReadAll to read contents of entire file.
|
||||
// This is similar to ioutil.ReadFile but without the call to os.Stat, because
|
||||
// many files in /proc and /sys report incorrect file sizes (either 0 or 4096).
|
||||
// Reads a max file size of 512kB. For files larger than this, a scanner
|
||||
// should be used.
|
||||
func ReadFileNoStat(filename string) ([]byte, error) {
|
||||
const maxBufferSize = 1024 * 512
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
reader := io.LimitReader(f, maxBufferSize)
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
+4
-1
@@ -23,6 +23,8 @@ import (
|
||||
|
||||
// SysReadFile is a simplified ioutil.ReadFile that invokes syscall.Read directly.
|
||||
// https://github.com/prometheus/node_exporter/pull/728/files
|
||||
//
|
||||
// Note that this function will not read files larger than 128 bytes.
|
||||
func SysReadFile(file string) (string, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
@@ -35,7 +37,8 @@ func SysReadFile(file string) (string, error) {
|
||||
//
|
||||
// Since we either want to read data or bail immediately, do the simplest
|
||||
// possible read using syscall directly.
|
||||
b := make([]byte, 128)
|
||||
const sysFileBufferSize = 128
|
||||
b := make([]byte, sysFileBufferSize)
|
||||
n, err := syscall.Read(int(f.Fd()), b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
+16
-2
@@ -33,6 +33,9 @@ func NewValueParser(v string) *ValueParser {
|
||||
return &ValueParser{v: v}
|
||||
}
|
||||
|
||||
// Int interprets the underlying value as an int and returns that value.
|
||||
func (vp *ValueParser) Int() int { return int(vp.int64()) }
|
||||
|
||||
// PInt64 interprets the underlying value as an int64 and returns a pointer to
|
||||
// that value.
|
||||
func (vp *ValueParser) PInt64() *int64 {
|
||||
@@ -40,16 +43,27 @@ func (vp *ValueParser) PInt64() *int64 {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := vp.int64()
|
||||
return &v
|
||||
}
|
||||
|
||||
// int64 interprets the underlying value as an int64 and returns that value.
|
||||
// TODO: export if/when necessary.
|
||||
func (vp *ValueParser) int64() int64 {
|
||||
if vp.err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// A base value of zero makes ParseInt infer the correct base using the
|
||||
// string's prefix, if any.
|
||||
const base = 0
|
||||
v, err := strconv.ParseInt(vp.v, base, 64)
|
||||
if err != nil {
|
||||
vp.err = err
|
||||
return nil
|
||||
return 0
|
||||
}
|
||||
|
||||
return &v
|
||||
return v
|
||||
}
|
||||
|
||||
// PUInt64 interprets the underlying value as an uint64 and returns a pointer to
|
||||
|
||||
+7
-5
@@ -15,6 +15,7 @@ package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -24,6 +25,8 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// IPVSStats holds IPVS statistics, as exposed by the kernel in `/proc/net/ip_vs_stats`.
|
||||
@@ -64,17 +67,16 @@ type IPVSBackendStatus struct {
|
||||
|
||||
// IPVSStats reads the IPVS statistics from the specified `proc` filesystem.
|
||||
func (fs FS) IPVSStats() (IPVSStats, error) {
|
||||
file, err := os.Open(fs.proc.Path("net/ip_vs_stats"))
|
||||
data, err := util.ReadFileNoStat(fs.proc.Path("net/ip_vs_stats"))
|
||||
if err != nil {
|
||||
return IPVSStats{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return parseIPVSStats(file)
|
||||
return parseIPVSStats(bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// parseIPVSStats performs the actual parsing of `ip_vs_stats`.
|
||||
func parseIPVSStats(file io.Reader) (IPVSStats, error) {
|
||||
func parseIPVSStats(r io.Reader) (IPVSStats, error) {
|
||||
var (
|
||||
statContent []byte
|
||||
statLines []string
|
||||
@@ -82,7 +84,7 @@ func parseIPVSStats(file io.Reader) (IPVSStats, error) {
|
||||
stats IPVSStats
|
||||
)
|
||||
|
||||
statContent, err := ioutil.ReadAll(file)
|
||||
statContent, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return IPVSStats{}, err
|
||||
}
|
||||
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// Meminfo represents memory statistics.
|
||||
type Meminfo struct {
|
||||
// Total usable ram (i.e. physical ram minus a few reserved
|
||||
// bits and the kernel binary code)
|
||||
MemTotal uint64
|
||||
// The sum of LowFree+HighFree
|
||||
MemFree uint64
|
||||
// An estimate of how much memory is available for starting
|
||||
// new applications, without swapping. Calculated from
|
||||
// MemFree, SReclaimable, the size of the file LRU lists, and
|
||||
// the low watermarks in each zone. The estimate takes into
|
||||
// account that the system needs some page cache to function
|
||||
// well, and that not all reclaimable slab will be
|
||||
// reclaimable, due to items being in use. The impact of those
|
||||
// factors will vary from system to system.
|
||||
MemAvailable uint64
|
||||
// Relatively temporary storage for raw disk blocks shouldn't
|
||||
// get tremendously large (20MB or so)
|
||||
Buffers uint64
|
||||
Cached uint64
|
||||
// Memory that once was swapped out, is swapped back in but
|
||||
// still also is in the swapfile (if memory is needed it
|
||||
// doesn't need to be swapped out AGAIN because it is already
|
||||
// in the swapfile. This saves I/O)
|
||||
SwapCached uint64
|
||||
// Memory that has been used more recently and usually not
|
||||
// reclaimed unless absolutely necessary.
|
||||
Active uint64
|
||||
// Memory which has been less recently used. It is more
|
||||
// eligible to be reclaimed for other purposes
|
||||
Inactive uint64
|
||||
ActiveAnon uint64
|
||||
InactiveAnon uint64
|
||||
ActiveFile uint64
|
||||
InactiveFile uint64
|
||||
Unevictable uint64
|
||||
Mlocked uint64
|
||||
// total amount of swap space available
|
||||
SwapTotal uint64
|
||||
// Memory which has been evicted from RAM, and is temporarily
|
||||
// on the disk
|
||||
SwapFree uint64
|
||||
// Memory which is waiting to get written back to the disk
|
||||
Dirty uint64
|
||||
// Memory which is actively being written back to the disk
|
||||
Writeback uint64
|
||||
// Non-file backed pages mapped into userspace page tables
|
||||
AnonPages uint64
|
||||
// files which have been mapped, such as libraries
|
||||
Mapped uint64
|
||||
Shmem uint64
|
||||
// in-kernel data structures cache
|
||||
Slab uint64
|
||||
// Part of Slab, that might be reclaimed, such as caches
|
||||
SReclaimable uint64
|
||||
// Part of Slab, that cannot be reclaimed on memory pressure
|
||||
SUnreclaim uint64
|
||||
KernelStack uint64
|
||||
// amount of memory dedicated to the lowest level of page
|
||||
// tables.
|
||||
PageTables uint64
|
||||
// NFS pages sent to the server, but not yet committed to
|
||||
// stable storage
|
||||
NFSUnstable uint64
|
||||
// Memory used for block device "bounce buffers"
|
||||
Bounce uint64
|
||||
// Memory used by FUSE for temporary writeback buffers
|
||||
WritebackTmp uint64
|
||||
// Based on the overcommit ratio ('vm.overcommit_ratio'),
|
||||
// this is the total amount of memory currently available to
|
||||
// be allocated on the system. This limit is only adhered to
|
||||
// if strict overcommit accounting is enabled (mode 2 in
|
||||
// 'vm.overcommit_memory').
|
||||
// The CommitLimit is calculated with the following formula:
|
||||
// CommitLimit = ([total RAM pages] - [total huge TLB pages]) *
|
||||
// overcommit_ratio / 100 + [total swap pages]
|
||||
// For example, on a system with 1G of physical RAM and 7G
|
||||
// of swap with a `vm.overcommit_ratio` of 30 it would
|
||||
// yield a CommitLimit of 7.3G.
|
||||
// For more details, see the memory overcommit documentation
|
||||
// in vm/overcommit-accounting.
|
||||
CommitLimit uint64
|
||||
// The amount of memory presently allocated on the system.
|
||||
// The committed memory is a sum of all of the memory which
|
||||
// has been allocated by processes, even if it has not been
|
||||
// "used" by them as of yet. A process which malloc()'s 1G
|
||||
// of memory, but only touches 300M of it will show up as
|
||||
// using 1G. This 1G is memory which has been "committed" to
|
||||
// by the VM and can be used at any time by the allocating
|
||||
// application. With strict overcommit enabled on the system
|
||||
// (mode 2 in 'vm.overcommit_memory'),allocations which would
|
||||
// exceed the CommitLimit (detailed above) will not be permitted.
|
||||
// This is useful if one needs to guarantee that processes will
|
||||
// not fail due to lack of memory once that memory has been
|
||||
// successfully allocated.
|
||||
CommittedAS uint64
|
||||
// total size of vmalloc memory area
|
||||
VmallocTotal uint64
|
||||
// amount of vmalloc area which is used
|
||||
VmallocUsed uint64
|
||||
// largest contiguous block of vmalloc area which is free
|
||||
VmallocChunk uint64
|
||||
HardwareCorrupted uint64
|
||||
AnonHugePages uint64
|
||||
ShmemHugePages uint64
|
||||
ShmemPmdMapped uint64
|
||||
CmaTotal uint64
|
||||
CmaFree uint64
|
||||
HugePagesTotal uint64
|
||||
HugePagesFree uint64
|
||||
HugePagesRsvd uint64
|
||||
HugePagesSurp uint64
|
||||
Hugepagesize uint64
|
||||
DirectMap4k uint64
|
||||
DirectMap2M uint64
|
||||
DirectMap1G uint64
|
||||
}
|
||||
|
||||
// Meminfo returns an information about current kernel/system memory statistics.
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
func (fs FS) Meminfo() (Meminfo, error) {
|
||||
b, err := util.ReadFileNoStat(fs.proc.Path("meminfo"))
|
||||
if err != nil {
|
||||
return Meminfo{}, err
|
||||
}
|
||||
|
||||
m, err := parseMemInfo(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return Meminfo{}, fmt.Errorf("failed to parse meminfo: %v", err)
|
||||
}
|
||||
|
||||
return *m, nil
|
||||
}
|
||||
|
||||
func parseMemInfo(r io.Reader) (*Meminfo, error) {
|
||||
var m Meminfo
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
// Each line has at least a name and value; we ignore the unit.
|
||||
fields := strings.Fields(s.Text())
|
||||
if len(fields) < 2 {
|
||||
return nil, fmt.Errorf("malformed meminfo line: %q", s.Text())
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(fields[1], 0, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch fields[0] {
|
||||
case "MemTotal:":
|
||||
m.MemTotal = v
|
||||
case "MemFree:":
|
||||
m.MemFree = v
|
||||
case "MemAvailable:":
|
||||
m.MemAvailable = v
|
||||
case "Buffers:":
|
||||
m.Buffers = v
|
||||
case "Cached:":
|
||||
m.Cached = v
|
||||
case "SwapCached:":
|
||||
m.SwapCached = v
|
||||
case "Active:":
|
||||
m.Active = v
|
||||
case "Inactive:":
|
||||
m.Inactive = v
|
||||
case "Active(anon):":
|
||||
m.ActiveAnon = v
|
||||
case "Inactive(anon):":
|
||||
m.InactiveAnon = v
|
||||
case "Active(file):":
|
||||
m.ActiveFile = v
|
||||
case "Inactive(file):":
|
||||
m.InactiveFile = v
|
||||
case "Unevictable:":
|
||||
m.Unevictable = v
|
||||
case "Mlocked:":
|
||||
m.Mlocked = v
|
||||
case "SwapTotal:":
|
||||
m.SwapTotal = v
|
||||
case "SwapFree:":
|
||||
m.SwapFree = v
|
||||
case "Dirty:":
|
||||
m.Dirty = v
|
||||
case "Writeback:":
|
||||
m.Writeback = v
|
||||
case "AnonPages:":
|
||||
m.AnonPages = v
|
||||
case "Mapped:":
|
||||
m.Mapped = v
|
||||
case "Shmem:":
|
||||
m.Shmem = v
|
||||
case "Slab:":
|
||||
m.Slab = v
|
||||
case "SReclaimable:":
|
||||
m.SReclaimable = v
|
||||
case "SUnreclaim:":
|
||||
m.SUnreclaim = v
|
||||
case "KernelStack:":
|
||||
m.KernelStack = v
|
||||
case "PageTables:":
|
||||
m.PageTables = v
|
||||
case "NFS_Unstable:":
|
||||
m.NFSUnstable = v
|
||||
case "Bounce:":
|
||||
m.Bounce = v
|
||||
case "WritebackTmp:":
|
||||
m.WritebackTmp = v
|
||||
case "CommitLimit:":
|
||||
m.CommitLimit = v
|
||||
case "Committed_AS:":
|
||||
m.CommittedAS = v
|
||||
case "VmallocTotal:":
|
||||
m.VmallocTotal = v
|
||||
case "VmallocUsed:":
|
||||
m.VmallocUsed = v
|
||||
case "VmallocChunk:":
|
||||
m.VmallocChunk = v
|
||||
case "HardwareCorrupted:":
|
||||
m.HardwareCorrupted = v
|
||||
case "AnonHugePages:":
|
||||
m.AnonHugePages = v
|
||||
case "ShmemHugePages:":
|
||||
m.ShmemHugePages = v
|
||||
case "ShmemPmdMapped:":
|
||||
m.ShmemPmdMapped = v
|
||||
case "CmaTotal:":
|
||||
m.CmaTotal = v
|
||||
case "CmaFree:":
|
||||
m.CmaFree = v
|
||||
case "HugePages_Total:":
|
||||
m.HugePagesTotal = v
|
||||
case "HugePages_Free:":
|
||||
m.HugePagesFree = v
|
||||
case "HugePages_Rsvd:":
|
||||
m.HugePagesRsvd = v
|
||||
case "HugePages_Surp:":
|
||||
m.HugePagesSurp = v
|
||||
case "Hugepagesize:":
|
||||
m.Hugepagesize = v
|
||||
case "DirectMap4k:":
|
||||
m.DirectMap4k = v
|
||||
case "DirectMap2M:":
|
||||
m.DirectMap2M = v
|
||||
case "DirectMap1G:":
|
||||
m.DirectMap1G = v
|
||||
}
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
+58
-56
@@ -15,19 +15,13 @@ package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var validOptionalFields = map[string]bool{
|
||||
"shared": true,
|
||||
"master": true,
|
||||
"propagate_from": true,
|
||||
"unbindable": true,
|
||||
}
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// A MountInfo is a type that describes the details, options
|
||||
// for each mount, parsed from /proc/self/mountinfo.
|
||||
@@ -58,18 +52,10 @@ type MountInfo struct {
|
||||
SuperOptions map[string]string
|
||||
}
|
||||
|
||||
// Returns part of the mountinfo line, if it exists, else an empty string.
|
||||
func getStringSliceElement(parts []string, idx int, defaultValue string) string {
|
||||
if idx >= len(parts) {
|
||||
return defaultValue
|
||||
}
|
||||
return parts[idx]
|
||||
}
|
||||
|
||||
// Reads each line of the mountinfo file, and returns a list of formatted MountInfo structs.
|
||||
func parseMountInfo(r io.Reader) ([]*MountInfo, error) {
|
||||
func parseMountInfo(info []byte) ([]*MountInfo, error) {
|
||||
mounts := []*MountInfo{}
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(info))
|
||||
for scanner.Scan() {
|
||||
mountString := scanner.Text()
|
||||
parsedMounts, err := parseMountInfoString(mountString)
|
||||
@@ -89,57 +75,75 @@ func parseMountInfo(r io.Reader) ([]*MountInfo, error) {
|
||||
func parseMountInfoString(mountString string) (*MountInfo, error) {
|
||||
var err error
|
||||
|
||||
// OptionalFields can be zero, hence these checks to ensure we do not populate the wrong values in the wrong spots
|
||||
separatorIndex := strings.Index(mountString, "-")
|
||||
if separatorIndex == -1 {
|
||||
return nil, fmt.Errorf("no separator found in mountinfo string: %s", mountString)
|
||||
mountInfo := strings.Split(mountString, " ")
|
||||
mountInfoLength := len(mountInfo)
|
||||
if mountInfoLength < 11 {
|
||||
return nil, fmt.Errorf("couldn't find enough fields in mount string: %s", mountString)
|
||||
}
|
||||
beforeFields := strings.Fields(mountString[:separatorIndex])
|
||||
afterFields := strings.Fields(mountString[separatorIndex+1:])
|
||||
if (len(beforeFields) + len(afterFields)) < 7 {
|
||||
return nil, fmt.Errorf("too few fields")
|
||||
|
||||
if mountInfo[mountInfoLength-4] != "-" {
|
||||
return nil, fmt.Errorf("couldn't find separator in expected field: %s", mountInfo[mountInfoLength-4])
|
||||
}
|
||||
|
||||
mount := &MountInfo{
|
||||
MajorMinorVer: getStringSliceElement(beforeFields, 2, ""),
|
||||
Root: getStringSliceElement(beforeFields, 3, ""),
|
||||
MountPoint: getStringSliceElement(beforeFields, 4, ""),
|
||||
Options: mountOptionsParser(getStringSliceElement(beforeFields, 5, "")),
|
||||
MajorMinorVer: mountInfo[2],
|
||||
Root: mountInfo[3],
|
||||
MountPoint: mountInfo[4],
|
||||
Options: mountOptionsParser(mountInfo[5]),
|
||||
OptionalFields: nil,
|
||||
FSType: getStringSliceElement(afterFields, 0, ""),
|
||||
Source: getStringSliceElement(afterFields, 1, ""),
|
||||
SuperOptions: mountOptionsParser(getStringSliceElement(afterFields, 2, "")),
|
||||
FSType: mountInfo[mountInfoLength-3],
|
||||
Source: mountInfo[mountInfoLength-2],
|
||||
SuperOptions: mountOptionsParser(mountInfo[mountInfoLength-1]),
|
||||
}
|
||||
|
||||
mount.MountId, err = strconv.Atoi(getStringSliceElement(beforeFields, 0, ""))
|
||||
mount.MountId, err = strconv.Atoi(mountInfo[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse mount ID")
|
||||
}
|
||||
mount.ParentId, err = strconv.Atoi(getStringSliceElement(beforeFields, 1, ""))
|
||||
mount.ParentId, err = strconv.Atoi(mountInfo[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse parent ID")
|
||||
}
|
||||
// Has optional fields, which is a space separated list of values.
|
||||
// Example: shared:2 master:7
|
||||
if len(beforeFields) > 6 {
|
||||
mount.OptionalFields = make(map[string]string)
|
||||
optionalFields := beforeFields[6:]
|
||||
for _, field := range optionalFields {
|
||||
optionSplit := strings.Split(field, ":")
|
||||
target, value := optionSplit[0], ""
|
||||
if len(optionSplit) == 2 {
|
||||
value = optionSplit[1]
|
||||
}
|
||||
// Checks if the 'keys' in the optional fields in the mountinfo line are acceptable.
|
||||
// Allowed 'keys' are shared, master, propagate_from, unbindable.
|
||||
if _, ok := validOptionalFields[target]; ok {
|
||||
mount.OptionalFields[target] = value
|
||||
}
|
||||
if mountInfo[6] != "" {
|
||||
mount.OptionalFields, err = mountOptionsParseOptionalFields(mountInfo[6 : mountInfoLength-4])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return mount, nil
|
||||
}
|
||||
|
||||
// mountOptionsIsValidField checks a string against a valid list of optional fields keys.
|
||||
func mountOptionsIsValidField(s string) bool {
|
||||
switch s {
|
||||
case
|
||||
"shared",
|
||||
"master",
|
||||
"propagate_from",
|
||||
"unbindable":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// mountOptionsParseOptionalFields parses a list of optional fields strings into a double map of strings.
|
||||
func mountOptionsParseOptionalFields(o []string) (map[string]string, error) {
|
||||
optionalFields := make(map[string]string)
|
||||
for _, field := range o {
|
||||
optionSplit := strings.SplitN(field, ":", 2)
|
||||
value := ""
|
||||
if len(optionSplit) == 2 {
|
||||
value = optionSplit[1]
|
||||
}
|
||||
if mountOptionsIsValidField(optionSplit[0]) {
|
||||
optionalFields[optionSplit[0]] = value
|
||||
}
|
||||
}
|
||||
return optionalFields, nil
|
||||
}
|
||||
|
||||
// Parses the mount options, superblock options.
|
||||
func mountOptionsParser(mountOptions string) map[string]string {
|
||||
opts := make(map[string]string)
|
||||
@@ -159,20 +163,18 @@ func mountOptionsParser(mountOptions string) map[string]string {
|
||||
|
||||
// Retrieves mountinfo information from `/proc/self/mountinfo`.
|
||||
func GetMounts() ([]*MountInfo, error) {
|
||||
f, err := os.Open("/proc/self/mountinfo")
|
||||
data, err := util.ReadFileNoStat("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return parseMountInfo(f)
|
||||
return parseMountInfo(data)
|
||||
}
|
||||
|
||||
// Retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`.
|
||||
func GetProcMounts(pid int) ([]*MountInfo, error) {
|
||||
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||
data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return parseMountInfo(f)
|
||||
return parseMountInfo(data)
|
||||
}
|
||||
|
||||
-1
@@ -183,7 +183,6 @@ func (netDev NetDev) Total() NetDevLine {
|
||||
names = append(names, ifc.Name)
|
||||
total.RxBytes += ifc.RxBytes
|
||||
total.RxPackets += ifc.RxPackets
|
||||
total.RxPackets += ifc.RxPackets
|
||||
total.RxErrors += ifc.RxErrors
|
||||
total.RxDropped += ifc.RxDropped
|
||||
total.RxFIFO += ifc.RxFIFO
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// A NetSockstat contains the output of /proc/net/sockstat{,6} for IPv4 or IPv6,
|
||||
// respectively.
|
||||
type NetSockstat struct {
|
||||
// Used is non-nil for IPv4 sockstat results, but nil for IPv6.
|
||||
Used *int
|
||||
Protocols []NetSockstatProtocol
|
||||
}
|
||||
|
||||
// A NetSockstatProtocol contains statistics about a given socket protocol.
|
||||
// Pointer fields indicate that the value may or may not be present on any
|
||||
// given protocol.
|
||||
type NetSockstatProtocol struct {
|
||||
Protocol string
|
||||
InUse int
|
||||
Orphan *int
|
||||
TW *int
|
||||
Alloc *int
|
||||
Mem *int
|
||||
Memory *int
|
||||
}
|
||||
|
||||
// NetSockstat retrieves IPv4 socket statistics.
|
||||
func (fs FS) NetSockstat() (*NetSockstat, error) {
|
||||
return readSockstat(fs.proc.Path("net", "sockstat"))
|
||||
}
|
||||
|
||||
// NetSockstat6 retrieves IPv6 socket statistics.
|
||||
//
|
||||
// If IPv6 is disabled on this kernel, the returned error can be checked with
|
||||
// os.IsNotExist.
|
||||
func (fs FS) NetSockstat6() (*NetSockstat, error) {
|
||||
return readSockstat(fs.proc.Path("net", "sockstat6"))
|
||||
}
|
||||
|
||||
// readSockstat opens and parses a NetSockstat from the input file.
|
||||
func readSockstat(name string) (*NetSockstat, error) {
|
||||
// This file is small and can be read with one syscall.
|
||||
b, err := util.ReadFileNoStat(name)
|
||||
if err != nil {
|
||||
// Do not wrap this error so the caller can detect os.IsNotExist and
|
||||
// similar conditions.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stat, err := parseSockstat(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read sockstats from %q: %v", name, err)
|
||||
}
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
// parseSockstat reads the contents of a sockstat file and parses a NetSockstat.
|
||||
func parseSockstat(r io.Reader) (*NetSockstat, error) {
|
||||
var stat NetSockstat
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
// Expect a minimum of a protocol and one key/value pair.
|
||||
fields := strings.Split(s.Text(), " ")
|
||||
if len(fields) < 3 {
|
||||
return nil, fmt.Errorf("malformed sockstat line: %q", s.Text())
|
||||
}
|
||||
|
||||
// The remaining fields are key/value pairs.
|
||||
kvs, err := parseSockstatKVs(fields[1:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing sockstat key/value pairs from %q: %v", s.Text(), err)
|
||||
}
|
||||
|
||||
// The first field is the protocol. We must trim its colon suffix.
|
||||
proto := strings.TrimSuffix(fields[0], ":")
|
||||
switch proto {
|
||||
case "sockets":
|
||||
// Special case: IPv4 has a sockets "used" key/value pair that we
|
||||
// embed at the top level of the structure.
|
||||
used := kvs["used"]
|
||||
stat.Used = &used
|
||||
default:
|
||||
// Parse all other lines as individual protocols.
|
||||
nsp := parseSockstatProtocol(kvs)
|
||||
nsp.Protocol = proto
|
||||
stat.Protocols = append(stat.Protocols, nsp)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stat, nil
|
||||
}
|
||||
|
||||
// parseSockstatKVs parses a string slice into a map of key/value pairs.
|
||||
func parseSockstatKVs(kvs []string) (map[string]int, error) {
|
||||
if len(kvs)%2 != 0 {
|
||||
return nil, errors.New("odd number of fields in key/value pairs")
|
||||
}
|
||||
|
||||
// Iterate two values at a time to gather key/value pairs.
|
||||
out := make(map[string]int, len(kvs)/2)
|
||||
for i := 0; i < len(kvs); i += 2 {
|
||||
vp := util.NewValueParser(kvs[i+1])
|
||||
out[kvs[i]] = vp.Int()
|
||||
|
||||
if err := vp.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// parseSockstatProtocol parses a NetSockstatProtocol from the input kvs map.
|
||||
func parseSockstatProtocol(kvs map[string]int) NetSockstatProtocol {
|
||||
var nsp NetSockstatProtocol
|
||||
for k, v := range kvs {
|
||||
// Capture the range variable to ensure we get unique pointers for
|
||||
// each of the optional fields.
|
||||
v := v
|
||||
switch k {
|
||||
case "inuse":
|
||||
nsp.InUse = v
|
||||
case "orphan":
|
||||
nsp.Orphan = &v
|
||||
case "tw":
|
||||
nsp.TW = &v
|
||||
case "alloc":
|
||||
nsp.Alloc = &v
|
||||
case "mem":
|
||||
nsp.Mem = &v
|
||||
case "memory":
|
||||
nsp.Memory = &v
|
||||
}
|
||||
}
|
||||
|
||||
return nsp
|
||||
}
|
||||
-4
@@ -207,10 +207,6 @@ func (u NetUnix) parseUsers(hexStr string) (uint64, error) {
|
||||
return strconv.ParseUint(hexStr, 16, 32)
|
||||
}
|
||||
|
||||
func (u NetUnix) parseProtocol(hexStr string) (uint64, error) {
|
||||
return strconv.ParseUint(hexStr, 16, 32)
|
||||
}
|
||||
|
||||
func (u NetUnix) parseType(hexStr string) (NetUnixType, error) {
|
||||
typ, err := strconv.ParseUint(hexStr, 16, 16)
|
||||
if err != nil {
|
||||
|
||||
+5
-18
@@ -22,6 +22,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/fs"
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// Proc provides information about a running process.
|
||||
@@ -121,13 +122,7 @@ func (fs FS) AllProcs() (Procs, error) {
|
||||
|
||||
// CmdLine returns the command line of a process.
|
||||
func (p Proc) CmdLine() ([]string, error) {
|
||||
f, err := os.Open(p.path("cmdline"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := util.ReadFileNoStat(p.path("cmdline"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -141,13 +136,7 @@ func (p Proc) CmdLine() ([]string, error) {
|
||||
|
||||
// Comm returns the command name of a process.
|
||||
func (p Proc) Comm() (string, error) {
|
||||
f, err := os.Open(p.path("comm"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := util.ReadFileNoStat(p.path("comm"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -252,13 +241,11 @@ func (p Proc) MountStats() ([]*Mount, error) {
|
||||
// It supplies information missing in `/proc/self/mounts` and
|
||||
// fixes various other problems with that file too.
|
||||
func (p Proc) MountInfo() ([]*MountInfo, error) {
|
||||
f, err := os.Open(p.path("mountinfo"))
|
||||
data, err := util.ReadFileNoStat(p.path("mountinfo"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return parseMountInfo(f)
|
||||
return parseMountInfo(data)
|
||||
}
|
||||
|
||||
func (p Proc) fileDescriptors() ([]string, error) {
|
||||
|
||||
+3
-9
@@ -14,22 +14,16 @@
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// Environ reads process environments from /proc/<pid>/environ
|
||||
func (p Proc) Environ() ([]string, error) {
|
||||
environments := make([]string, 0)
|
||||
|
||||
f, err := os.Open(p.path("environ"))
|
||||
if err != nil {
|
||||
return environments, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := util.ReadFileNoStat(p.path("environ"))
|
||||
if err != nil {
|
||||
return environments, err
|
||||
}
|
||||
|
||||
+5
-12
@@ -15,11 +15,10 @@ package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// Regexp variables
|
||||
@@ -46,21 +45,15 @@ type ProcFDInfo struct {
|
||||
|
||||
// FDInfo constructor. On kernels older than 3.8, InotifyInfos will always be empty.
|
||||
func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) {
|
||||
f, err := os.Open(p.path("fdinfo", fd))
|
||||
data, err := util.ReadFileNoStat(p.path("fdinfo", fd))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fdinfo, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read %s: %s", f.Name(), err)
|
||||
}
|
||||
|
||||
var text, pos, flags, mntid string
|
||||
var inotify []InotifyInfo
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(fdinfo)))
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
text = scanner.Text()
|
||||
if rPos.MatchString(text) {
|
||||
|
||||
+3
-9
@@ -15,8 +15,8 @@ package procfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// ProcIO models the content of /proc/<pid>/io.
|
||||
@@ -43,13 +43,7 @@ type ProcIO struct {
|
||||
func (p Proc) IO() (ProcIO, error) {
|
||||
pio := ProcIO{}
|
||||
|
||||
f, err := os.Open(p.path("io"))
|
||||
if err != nil {
|
||||
return pio, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := util.ReadFileNoStat(p.path("io"))
|
||||
if err != nil {
|
||||
return pio, err
|
||||
}
|
||||
|
||||
+10
-11
@@ -24,11 +24,13 @@ package procfs
|
||||
// > full avg10=0.00 avg60=0.13 avg300=0.96 total=8183134
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
const lineFormat = "avg10=%f avg60=%f avg300=%f total=%d"
|
||||
@@ -55,24 +57,21 @@ type PSIStats struct {
|
||||
// resource from /proc/pressure/<resource>. At time of writing this can be
|
||||
// either "cpu", "memory" or "io".
|
||||
func (fs FS) PSIStatsForResource(resource string) (PSIStats, error) {
|
||||
file, err := os.Open(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
|
||||
data, err := util.ReadFileNoStat(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
|
||||
if err != nil {
|
||||
return PSIStats{}, fmt.Errorf("psi_stats: unavailable for %s", resource)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
return parsePSIStats(resource, file)
|
||||
return parsePSIStats(resource, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// parsePSIStats parses the specified file for pressure stall information
|
||||
func parsePSIStats(resource string, file io.Reader) (PSIStats, error) {
|
||||
func parsePSIStats(resource string, r io.Reader) (PSIStats, error) {
|
||||
psiStats := PSIStats{}
|
||||
stats, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return psiStats, fmt.Errorf("psi_stats: unable to read data for %s", resource)
|
||||
}
|
||||
|
||||
for _, l := range strings.Split(string(stats), "\n") {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
l := scanner.Text()
|
||||
prefix := strings.Split(l, " ")[0]
|
||||
switch prefix {
|
||||
case "some":
|
||||
|
||||
+2
-8
@@ -16,10 +16,10 @@ package procfs
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/prometheus/procfs/internal/fs"
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// Originally, this USER_HZ value was dynamically retrieved via a sysconf call
|
||||
@@ -113,13 +113,7 @@ func (p Proc) NewStat() (ProcStat, error) {
|
||||
|
||||
// Stat returns the current status information of the process.
|
||||
func (p Proc) Stat() (ProcStat, error) {
|
||||
f, err := os.Open(p.path("stat"))
|
||||
if err != nil {
|
||||
return ProcStat{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := util.ReadFileNoStat(p.path("stat"))
|
||||
if err != nil {
|
||||
return ProcStat{}, err
|
||||
}
|
||||
|
||||
+9
-10
@@ -15,13 +15,13 @@ package procfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// ProcStat provides status information about the process,
|
||||
// ProcStatus provides status information about the process,
|
||||
// read from /proc/[pid]/stat.
|
||||
type ProcStatus struct {
|
||||
// The process ID.
|
||||
@@ -29,6 +29,9 @@ type ProcStatus struct {
|
||||
// The process name.
|
||||
Name string
|
||||
|
||||
// Thread group ID.
|
||||
TGID int
|
||||
|
||||
// Peak virtual memory size.
|
||||
VmPeak uint64
|
||||
// Virtual memory size.
|
||||
@@ -72,13 +75,7 @@ type ProcStatus struct {
|
||||
|
||||
// NewStatus returns the current status information of the process.
|
||||
func (p Proc) NewStatus() (ProcStatus, error) {
|
||||
f, err := os.Open(p.path("status"))
|
||||
if err != nil {
|
||||
return ProcStatus{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := util.ReadFileNoStat(p.path("status"))
|
||||
if err != nil {
|
||||
return ProcStatus{}, err
|
||||
}
|
||||
@@ -113,6 +110,8 @@ func (p Proc) NewStatus() (ProcStatus, error) {
|
||||
|
||||
func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintBytes uint64) {
|
||||
switch k {
|
||||
case "Tgid":
|
||||
s.TGID = int(vUint)
|
||||
case "Name":
|
||||
s.Name = vString
|
||||
case "VmPeak":
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ var (
|
||||
procLineRE = regexp.MustCompile(`(\d+) (\d+) (\d+)`)
|
||||
)
|
||||
|
||||
// Schedstat contains scheduler statistics from /proc/schedstats
|
||||
// Schedstat contains scheduler statistics from /proc/schedstat
|
||||
//
|
||||
// See
|
||||
// https://www.kernel.org/doc/Documentation/scheduler/sched-stats.txt
|
||||
@@ -36,7 +36,6 @@ var (
|
||||
// jiffies when they are actually in nanoseconds since 2.6.23 with the
|
||||
// introduction of CFS. A fix to the documentation is pending. See
|
||||
// https://lore.kernel.org/patchwork/project/lkml/list/?series=403473
|
||||
|
||||
type Schedstat struct {
|
||||
CPUs []*SchedstatCPU
|
||||
}
|
||||
@@ -57,6 +56,7 @@ type ProcSchedstat struct {
|
||||
RunTimeslices uint64
|
||||
}
|
||||
|
||||
// Schedstat reads data from /proc/schedstat
|
||||
func (fs FS) Schedstat() (*Schedstat, error) {
|
||||
file, err := os.Open(fs.proc.Path("schedstat"))
|
||||
if err != nil {
|
||||
|
||||
+6
-6
@@ -15,13 +15,14 @@ package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/fs"
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// CPUStat shows how much time the cpu spend in various stages.
|
||||
@@ -164,16 +165,15 @@ func (fs FS) NewStat() (Stat, error) {
|
||||
// Stat returns information about current cpu/process statistics.
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
func (fs FS) Stat() (Stat, error) {
|
||||
|
||||
f, err := os.Open(fs.proc.Path("stat"))
|
||||
fileName := fs.proc.Path("stat")
|
||||
data, err := util.ReadFileNoStat(fileName)
|
||||
if err != nil {
|
||||
return Stat{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stat := Stat{}
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Fields(scanner.Text())
|
||||
@@ -237,7 +237,7 @@ func (fs FS) Stat() (Stat, error) {
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return Stat{}, fmt.Errorf("couldn't parse %s: %s", f.Name(), err)
|
||||
return Stat{}, fmt.Errorf("couldn't parse %s: %s", fileName, err)
|
||||
}
|
||||
|
||||
return stat, nil
|
||||
|
||||
+7
-12
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -28,7 +28,6 @@ type TimesStat struct {
|
||||
Steal float64 `json:"steal"`
|
||||
Guest float64 `json:"guest"`
|
||||
GuestNice float64 `json:"guestNice"`
|
||||
Stolen float64 `json:"stolen"`
|
||||
}
|
||||
|
||||
type InfoStat struct {
|
||||
@@ -63,14 +62,11 @@ func init() {
|
||||
lastCPUPercent.Unlock()
|
||||
}
|
||||
|
||||
// Counts returns the number of physical or logical cores in the system
|
||||
func Counts(logical bool) (int, error) {
|
||||
return CountsWithContext(context.Background(), logical)
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
|
||||
func (c TimesStat) String() string {
|
||||
v := []string{
|
||||
`"cpu":"` + c.CPU + `"`,
|
||||
@@ -84,7 +80,6 @@ func (c TimesStat) String() string {
|
||||
`"steal":` + strconv.FormatFloat(c.Steal, 'f', 1, 64),
|
||||
`"guest":` + strconv.FormatFloat(c.Guest, 'f', 1, 64),
|
||||
`"guestNice":` + strconv.FormatFloat(c.GuestNice, 'f', 1, 64),
|
||||
`"stolen":` + strconv.FormatFloat(c.Stolen, 'f', 1, 64),
|
||||
}
|
||||
|
||||
return `{` + strings.Join(v, ",") + `}`
|
||||
@@ -92,8 +87,8 @@ func (c TimesStat) String() string {
|
||||
|
||||
// Total returns the total number of seconds in a CPUTimesStat
|
||||
func (c TimesStat) Total() float64 {
|
||||
total := c.User + c.System + c.Nice + c.Iowait + c.Irq + c.Softirq + c.Steal +
|
||||
c.Guest + c.GuestNice + c.Idle + c.Stolen
|
||||
total := c.User + c.System + c.Nice + c.Iowait + c.Irq + c.Softirq +
|
||||
c.Steal + c.Idle
|
||||
return total
|
||||
}
|
||||
|
||||
@@ -104,7 +99,7 @@ func (c InfoStat) String() string {
|
||||
|
||||
func getAllBusy(t TimesStat) (float64, float64) {
|
||||
busy := t.User + t.System + t.Nice + t.Iowait + t.Irq +
|
||||
t.Softirq + t.Steal + t.Guest + t.GuestNice + t.Stolen
|
||||
t.Softirq + t.Steal
|
||||
return busy + t.Idle, busy
|
||||
}
|
||||
|
||||
@@ -116,9 +111,9 @@ func calculateBusy(t1, t2 TimesStat) float64 {
|
||||
return 0
|
||||
}
|
||||
if t2All <= t1All {
|
||||
return 1
|
||||
return 100
|
||||
}
|
||||
return (t2Busy - t1Busy) / (t2All - t1All) * 100
|
||||
return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100))
|
||||
}
|
||||
|
||||
func calculateAllBusy(t1, t2 []TimesStat) ([]float64, error) {
|
||||
|
||||
+48
-60
@@ -4,9 +4,10 @@ package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// sys/resource.h
|
||||
@@ -41,75 +42,62 @@ func Info() ([]InfoStat, error) {
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
sysctl, err := exec.LookPath("/usr/sbin/sysctl")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, sysctl, "machdep.cpu")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
c := InfoStat{}
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
values := strings.Fields(line)
|
||||
if len(values) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := strconv.ParseInt(values[1], 10, 64)
|
||||
// err is not checked here because some value is string.
|
||||
if strings.HasPrefix(line, "machdep.cpu.brand_string") {
|
||||
c.ModelName = strings.Join(values[1:], " ")
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.family") {
|
||||
c.Family = values[1]
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.model") {
|
||||
c.Model = values[1]
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.stepping") {
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.Stepping = int32(t)
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.features") {
|
||||
for _, v := range values[1:] {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.leaf7_features") {
|
||||
for _, v := range values[1:] {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.extfeatures") {
|
||||
for _, v := range values[1:] {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.core_count") {
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.Cores = int32(t)
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.cache.size") {
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.CacheSize = int32(t)
|
||||
} else if strings.HasPrefix(line, "machdep.cpu.vendor") {
|
||||
c.VendorID = values[1]
|
||||
c.ModelName, _ = unix.Sysctl("machdep.cpu.brand_string")
|
||||
family, _ := unix.SysctlUint32("machdep.cpu.family")
|
||||
c.Family = strconv.FormatUint(uint64(family), 10)
|
||||
model, _ := unix.SysctlUint32("machdep.cpu.model")
|
||||
c.Model = strconv.FormatUint(uint64(model), 10)
|
||||
stepping, _ := unix.SysctlUint32("machdep.cpu.stepping")
|
||||
c.Stepping = int32(stepping)
|
||||
features, err := unix.Sysctl("machdep.cpu.features")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(features) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
leaf7Features, err := unix.Sysctl("machdep.cpu.leaf7_features")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(leaf7Features) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
extfeatures, err := unix.Sysctl("machdep.cpu.extfeatures")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(extfeatures) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
cores, _ := unix.SysctlUint32("machdep.cpu.core_count")
|
||||
c.Cores = int32(cores)
|
||||
cacheSize, _ := unix.SysctlUint32("machdep.cpu.cache.size")
|
||||
c.CacheSize = int32(cacheSize)
|
||||
c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor")
|
||||
|
||||
// Use the rated frequency of the CPU. This is a static value and does not
|
||||
// account for low power or Turbo Boost modes.
|
||||
out, err = invoke.CommandWithContext(ctx, sysctl, "hw.cpufrequency")
|
||||
cpuFrequency, err := unix.SysctlUint64("hw.cpufrequency")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
values := strings.Fields(string(out))
|
||||
hz, err := strconv.ParseFloat(values[1], 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.Mhz = hz / 1000000.0
|
||||
c.Mhz = float64(cpuFrequency) / 1000000.0
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
var cpuArgument string
|
||||
if logical {
|
||||
cpuArgument = "hw.logicalcpu"
|
||||
} else {
|
||||
cpuArgument = "hw.physicalcpu"
|
||||
}
|
||||
|
||||
count, err := unix.SysctlUint32(cpuArgument)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
+5
@@ -4,6 +4,7 @@ package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
@@ -23,3 +24,7 @@ func Info() ([]InfoStat, error) {
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
return []InfoStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
|
||||
+6
-1
@@ -6,6 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
@@ -25,7 +26,7 @@ var cpuTimesSize int
|
||||
var emptyTimes cpuTimes
|
||||
|
||||
func init() {
|
||||
getconf, err := exec.LookPath("/usr/bin/getconf")
|
||||
getconf, err := exec.LookPath("getconf")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -166,3 +167,7 @@ func parseDmesgBoot(fileName string) (InfoStat, int, error) {
|
||||
|
||||
return c, cpuNum, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+70
-2
@@ -16,7 +16,7 @@ import (
|
||||
var CPUTick = float64(100)
|
||||
|
||||
func init() {
|
||||
getconf, err := exec.LookPath("/usr/bin/getconf")
|
||||
getconf, err := exec.LookPath("getconf")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func finishCPUInfo(c *InfoStat) error {
|
||||
lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq"))
|
||||
// if we encounter errors below such as there are no cpuinfo_max_freq file,
|
||||
// we just ignore. so let Mhz is 0.
|
||||
if err != nil {
|
||||
if err != nil || len(lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
value, err = strconv.ParseFloat(lines[0], 64)
|
||||
@@ -282,3 +282,71 @@ func parseStatLine(line string) (*TimesStat, error) {
|
||||
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
if logical {
|
||||
ret := 0
|
||||
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599
|
||||
procCpuinfo := common.HostProc("cpuinfo")
|
||||
lines, err := common.ReadLines(procCpuinfo)
|
||||
if err == nil {
|
||||
for _, line := range lines {
|
||||
line = strings.ToLower(line)
|
||||
if strings.HasPrefix(line, "processor") {
|
||||
ret++
|
||||
}
|
||||
}
|
||||
}
|
||||
if ret == 0 {
|
||||
procStat := common.HostProc("stat")
|
||||
lines, err = common.ReadLines(procStat)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, line := range lines {
|
||||
if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching
|
||||
ret++
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
// physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L628
|
||||
filename := common.HostProc("cpuinfo")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
mapping := make(map[int]int)
|
||||
currentInfo := make(map[string]int)
|
||||
for _, line := range lines {
|
||||
line = strings.ToLower(strings.TrimSpace(line))
|
||||
if line == "" {
|
||||
// new section
|
||||
id, okID := currentInfo["physical id"]
|
||||
cores, okCores := currentInfo["cpu cores"]
|
||||
if okID && okCores {
|
||||
mapping[id] = cores
|
||||
}
|
||||
currentInfo = make(map[string]int)
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
fields[0] = strings.TrimSpace(fields[0])
|
||||
if fields[0] == "physical id" || fields[0] == "cpu cores" {
|
||||
val, err := strconv.Atoi(strings.TrimSpace(fields[1]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
currentInfo[fields[0]] = val
|
||||
}
|
||||
}
|
||||
ret := 0
|
||||
for _, v := range mapping {
|
||||
ret += v
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
+95
-19
@@ -8,15 +8,17 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// sys/sched.h
|
||||
const (
|
||||
var (
|
||||
CPUser = 0
|
||||
CPNice = 1
|
||||
CPSys = 2
|
||||
@@ -28,6 +30,9 @@ const (
|
||||
// sys/sysctl.h
|
||||
const (
|
||||
CTLKern = 1 // "high kernel": proc, limits
|
||||
CTLHw = 6 // CTL_HW
|
||||
SMT = 24 // HW_SMT
|
||||
NCpuOnline = 25 // HW_NCPUONLINE
|
||||
KernCptime = 40 // KERN_CPTIME
|
||||
KernCptime2 = 71 // KERN_CPTIME2
|
||||
)
|
||||
@@ -35,18 +40,52 @@ const (
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
func init() {
|
||||
getconf, err := exec.LookPath("/usr/bin/getconf")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err := invoke.Command(getconf, "CLK_TCK")
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(i)
|
||||
func() {
|
||||
getconf, err := exec.LookPath("getconf")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err := invoke.Command(getconf, "CLK_TCK")
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(i)
|
||||
}
|
||||
}
|
||||
}()
|
||||
func() {
|
||||
v, err := unix.Sysctl("kern.osrelease") // can't reuse host.PlatformInformation because of circular import
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
v = strings.ToLower(v)
|
||||
version, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if version >= 6.4 {
|
||||
CPIntr = 4
|
||||
CPIdle = 5
|
||||
CPUStates = 6
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func smt() (bool, error) {
|
||||
mib := []int32{CTLHw, SMT}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var ret bool
|
||||
br := bytes.NewReader(buf)
|
||||
if err := binary.Read(br, binary.LittleEndian, &ret); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
@@ -63,13 +102,27 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
ncpu = 1
|
||||
}
|
||||
|
||||
smt, err := smt()
|
||||
if err == syscall.EOPNOTSUPP {
|
||||
// if hw.smt is not applicable for this platform (e.g. i386),
|
||||
// pretend it's enabled
|
||||
smt = true
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < ncpu; i++ {
|
||||
var cpuTimes [CPUStates]int64
|
||||
j := i
|
||||
if !smt {
|
||||
j *= 2
|
||||
}
|
||||
|
||||
var cpuTimes = make([]int32, CPUStates)
|
||||
var mib []int32
|
||||
if percpu {
|
||||
mib = []int32{CTLKern, KernCptime}
|
||||
mib = []int32{CTLKern, KernCptime2, int32(j)}
|
||||
} else {
|
||||
mib = []int32{CTLKern, KernCptime2, int32(i)}
|
||||
mib = []int32{CTLKern, KernCptime}
|
||||
}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
@@ -88,10 +141,10 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
Idle: float64(cpuTimes[CPIdle]) / ClocksPerSec,
|
||||
Irq: float64(cpuTimes[CPIntr]) / ClocksPerSec,
|
||||
}
|
||||
if !percpu {
|
||||
c.CPU = "cpu-total"
|
||||
if percpu {
|
||||
c.CPU = fmt.Sprintf("cpu%d", j)
|
||||
} else {
|
||||
c.CPU = fmt.Sprintf("cpu%d", i)
|
||||
c.CPU = "cpu-total"
|
||||
}
|
||||
ret = append(ret, c)
|
||||
}
|
||||
@@ -106,14 +159,37 @@ func Info() ([]InfoStat, error) {
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
var err error
|
||||
|
||||
c := InfoStat{}
|
||||
|
||||
v, err := unix.Sysctl("hw.model")
|
||||
var u32 uint32
|
||||
if u32, err = unix.SysctlUint32("hw.cpuspeed"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Mhz = float64(u32)
|
||||
|
||||
mib := []int32{CTLHw, NCpuOnline}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.ModelName = v
|
||||
|
||||
var ncpu int32
|
||||
br := bytes.NewReader(buf)
|
||||
err = binary.Read(br, binary.LittleEndian, &ncpu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cores = ncpu
|
||||
|
||||
if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
|
||||
+94
-6
@@ -6,17 +6,16 @@ import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
func init() {
|
||||
getconf, err := exec.LookPath("/usr/bin/getconf")
|
||||
getconf, err := exec.LookPath("getconf")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -30,12 +29,97 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
//sum all values in a float64 map with float64 keys
|
||||
func msum(x map[float64]float64) float64 {
|
||||
total := 0.0
|
||||
for _, y := range x {
|
||||
total += y
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
kstatSys, err := exec.LookPath("kstat")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find kstat: %s", err)
|
||||
}
|
||||
cpu := make(map[float64]float64)
|
||||
idle := make(map[float64]float64)
|
||||
user := make(map[float64]float64)
|
||||
kern := make(map[float64]float64)
|
||||
iowt := make(map[float64]float64)
|
||||
//swap := make(map[float64]float64)
|
||||
kstatSysOut, err := invoke.CommandWithContext(ctx, kstatSys, "-p", "cpu_stat:*:*:/^idle$|^user$|^kernel$|^iowait$|^swap$/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot execute kstat: %s", err)
|
||||
}
|
||||
re := regexp.MustCompile(`[:\s]+`)
|
||||
for _, line := range strings.Split(string(kstatSysOut), "\n") {
|
||||
fields := re.Split(line, -1)
|
||||
if fields[0] != "cpu_stat" {
|
||||
continue
|
||||
}
|
||||
cpuNumber, err := strconv.ParseFloat(fields[1], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse cpu number: %s", err)
|
||||
}
|
||||
cpu[cpuNumber] = cpuNumber
|
||||
switch fields[3] {
|
||||
case "idle":
|
||||
idle[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse idle: %s", err)
|
||||
}
|
||||
case "user":
|
||||
user[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse user: %s", err)
|
||||
}
|
||||
case "kernel":
|
||||
kern[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse kernel: %s", err)
|
||||
}
|
||||
case "iowait":
|
||||
iowt[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse iowait: %s", err)
|
||||
}
|
||||
//not sure how this translates, don't report, add to kernel, something else?
|
||||
/*case "swap":
|
||||
swap[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse swap: %s", err)
|
||||
} */
|
||||
}
|
||||
}
|
||||
ret := make([]TimesStat, 0, len(cpu))
|
||||
if percpu {
|
||||
for _, c := range cpu {
|
||||
ct := &TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", int(cpu[c])),
|
||||
Idle: idle[c] / ClocksPerSec,
|
||||
User: user[c] / ClocksPerSec,
|
||||
System: kern[c] / ClocksPerSec,
|
||||
Iowait: iowt[c] / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
} else {
|
||||
ct := &TimesStat{
|
||||
CPU: "cpu-total",
|
||||
Idle: msum(idle) / ClocksPerSec,
|
||||
User: msum(user) / ClocksPerSec,
|
||||
System: msum(kern) / ClocksPerSec,
|
||||
Iowait: msum(iowt) / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
@@ -43,7 +127,7 @@ func Info() ([]InfoStat, error) {
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
psrInfo, err := exec.LookPath("/usr/sbin/psrinfo")
|
||||
psrInfo, err := exec.LookPath("psrinfo")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find psrinfo: %s", err)
|
||||
}
|
||||
@@ -52,7 +136,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
return nil, fmt.Errorf("cannot execute psrinfo: %s", err)
|
||||
}
|
||||
|
||||
isaInfo, err := exec.LookPath("/usr/bin/isainfo")
|
||||
isaInfo, err := exec.LookPath("isainfo")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find isainfo: %s", err)
|
||||
}
|
||||
@@ -196,3 +280,7 @@ func parseProcessorInfo(cmdOutput string) ([]InfoStat, error) {
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
|
||||
+119
-35
@@ -12,29 +12,35 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
procGetActiveProcessorCount = common.Modkernel32.NewProc("GetActiveProcessorCount")
|
||||
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
|
||||
)
|
||||
|
||||
type Win32_Processor struct {
|
||||
LoadPercentage *uint16
|
||||
Family uint16
|
||||
Manufacturer string
|
||||
Name string
|
||||
NumberOfLogicalProcessors uint32
|
||||
NumberOfCores uint32
|
||||
ProcessorID *string
|
||||
Stepping *string
|
||||
MaxClockSpeed uint32
|
||||
}
|
||||
|
||||
type win32_PerfRawData_Counters_ProcessorInformation struct {
|
||||
Name string
|
||||
PercentDPCTime uint64
|
||||
PercentIdleTime uint64
|
||||
PercentUserTime uint64
|
||||
PercentProcessorTime uint64
|
||||
PercentInterruptTime uint64
|
||||
PercentPriorityTime uint64
|
||||
PercentPrivilegedTime uint64
|
||||
InterruptsPerSec uint32
|
||||
ProcessorFrequency uint32
|
||||
DPCRate uint32
|
||||
// SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
|
||||
// defined in windows api doc with the following
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntquerysysteminformation#system_processor_performance_information
|
||||
// additional fields documented here
|
||||
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/processor_performance.htm
|
||||
type win32_SystemProcessorPerformanceInformation struct {
|
||||
IdleTime int64 // idle time in 100ns (this is not a filetime).
|
||||
KernelTime int64 // kernel time in 100ns. kernel time includes idle time. (this is not a filetime).
|
||||
UserTime int64 // usertime in 100ns (this is not a filetime).
|
||||
DpcTime int64 // dpc time in 100ns (this is not a filetime).
|
||||
InterruptTime int64 // interrupt time in 100ns
|
||||
InterruptCount uint32
|
||||
}
|
||||
|
||||
// Win32_PerfFormattedData_PerfOS_System struct to have count of processes and processor queue length
|
||||
@@ -45,6 +51,13 @@ type Win32_PerfFormattedData_PerfOS_System struct {
|
||||
|
||||
const (
|
||||
win32_TicksPerSecond = 10000000.0
|
||||
|
||||
// systemProcessorPerformanceInformationClass information class to query with NTQuerySystemInformation
|
||||
// https://processhacker.sourceforge.io/doc/ntexapi_8h.html#ad5d815b48e8f4da1ef2eb7a2f18a54e0
|
||||
win32_SystemProcessorPerformanceInformationClass = 8
|
||||
|
||||
// size of systemProcessorPerformanceInfoSize in memory
|
||||
win32_SystemProcessorPerformanceInfoSize = uint32(unsafe.Sizeof(win32_SystemProcessorPerformanceInformation{}))
|
||||
)
|
||||
|
||||
// Times returns times stat per cpu and combined for all CPUs
|
||||
@@ -54,7 +67,7 @@ func Times(percpu bool) ([]TimesStat, error) {
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
if percpu {
|
||||
return perCPUTimesWithContext(ctx)
|
||||
return perCPUTimes()
|
||||
}
|
||||
|
||||
var ret []TimesStat
|
||||
@@ -120,20 +133,6 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// PerfInfo returns the performance counter's instance value for ProcessorInformation.
|
||||
// Name property is the key by which overall, per cpu and per core metric is known.
|
||||
func perfInfoWithContext(ctx context.Context) ([]win32_PerfRawData_Counters_ProcessorInformation, error) {
|
||||
var ret []win32_PerfRawData_Counters_ProcessorInformation
|
||||
|
||||
q := wmi.CreateQuery(&ret, "WHERE NOT Name LIKE '%_Total'")
|
||||
err := common.WMIQueryWithContext(ctx, q, &ret)
|
||||
if err != nil {
|
||||
return []win32_PerfRawData_Counters_ProcessorInformation{}, err
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// ProcInfo returns processes count and processor queue length in the system.
|
||||
// There is a single queue for processor even on multiprocessors systems.
|
||||
func ProcInfo() ([]Win32_PerfFormattedData_PerfOS_System, error) {
|
||||
@@ -151,21 +150,106 @@ func ProcInfoWithContext(ctx context.Context) ([]Win32_PerfFormattedData_PerfOS_
|
||||
}
|
||||
|
||||
// perCPUTimes returns times stat per cpu, per core and overall for all CPUs
|
||||
func perCPUTimesWithContext(ctx context.Context) ([]TimesStat, error) {
|
||||
func perCPUTimes() ([]TimesStat, error) {
|
||||
var ret []TimesStat
|
||||
stats, err := perfInfoWithContext(ctx)
|
||||
stats, err := perfInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range stats {
|
||||
for core, v := range stats {
|
||||
c := TimesStat{
|
||||
CPU: v.Name,
|
||||
User: float64(v.PercentUserTime) / win32_TicksPerSecond,
|
||||
System: float64(v.PercentPrivilegedTime) / win32_TicksPerSecond,
|
||||
Idle: float64(v.PercentIdleTime) / win32_TicksPerSecond,
|
||||
Irq: float64(v.PercentInterruptTime) / win32_TicksPerSecond,
|
||||
CPU: fmt.Sprintf("cpu%d", core),
|
||||
User: float64(v.UserTime) / win32_TicksPerSecond,
|
||||
System: float64(v.KernelTime-v.IdleTime) / win32_TicksPerSecond,
|
||||
Idle: float64(v.IdleTime) / win32_TicksPerSecond,
|
||||
Irq: float64(v.InterruptTime) / win32_TicksPerSecond,
|
||||
}
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// makes call to Windows API function to retrieve performance information for each core
|
||||
func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) {
|
||||
// Make maxResults large for safety.
|
||||
// We can't invoke the api call with a results array that's too small.
|
||||
// If we have more than 2056 cores on a single host, then it's probably the future.
|
||||
maxBuffer := 2056
|
||||
// buffer for results from the windows proc
|
||||
resultBuffer := make([]win32_SystemProcessorPerformanceInformation, maxBuffer)
|
||||
// size of the buffer in memory
|
||||
bufferSize := uintptr(win32_SystemProcessorPerformanceInfoSize) * uintptr(maxBuffer)
|
||||
// size of the returned response
|
||||
var retSize uint32
|
||||
|
||||
// Invoke windows api proc.
|
||||
// The returned err from the windows dll proc will always be non-nil even when successful.
|
||||
// See https://godoc.org/golang.org/x/sys/windows#LazyProc.Call for more information
|
||||
retCode, _, err := common.ProcNtQuerySystemInformation.Call(
|
||||
win32_SystemProcessorPerformanceInformationClass, // System Information Class -> SystemProcessorPerformanceInformation
|
||||
uintptr(unsafe.Pointer(&resultBuffer[0])), // pointer to first element in result buffer
|
||||
bufferSize, // size of the buffer in memory
|
||||
uintptr(unsafe.Pointer(&retSize)), // pointer to the size of the returned results the windows proc will set this
|
||||
)
|
||||
|
||||
// check return code for errors
|
||||
if retCode != 0 {
|
||||
return nil, fmt.Errorf("call to NtQuerySystemInformation returned %d. err: %s", retCode, err.Error())
|
||||
}
|
||||
|
||||
// calculate the number of returned elements based on the returned size
|
||||
numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize
|
||||
|
||||
// trim results to the number of returned elements
|
||||
resultBuffer = resultBuffer[:numReturnedElements]
|
||||
|
||||
return resultBuffer, nil
|
||||
}
|
||||
|
||||
// SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API.
|
||||
// https://msdn.microsoft.com/en-us/library/ms724958%28VS.85%29.aspx?f=255&MSPPError=-2147217396
|
||||
// https://github.com/elastic/go-windows/blob/bb1581babc04d5cb29a2bfa7a9ac6781c730c8dd/kernel32.go#L43
|
||||
type systemInfo struct {
|
||||
wProcessorArchitecture uint16
|
||||
wReserved uint16
|
||||
dwPageSize uint32
|
||||
lpMinimumApplicationAddress uintptr
|
||||
lpMaximumApplicationAddress uintptr
|
||||
dwActiveProcessorMask uintptr
|
||||
dwNumberOfProcessors uint32
|
||||
dwProcessorType uint32
|
||||
dwAllocationGranularity uint32
|
||||
wProcessorLevel uint16
|
||||
wProcessorRevision uint16
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
if logical {
|
||||
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97
|
||||
err := procGetActiveProcessorCount.Find()
|
||||
if err == nil { // Win7+
|
||||
ret, _, _ := procGetActiveProcessorCount.Call(uintptr(0xffff)) // ALL_PROCESSOR_GROUPS is 0xffff according to Rust's winapi lib https://docs.rs/winapi/*/x86_64-pc-windows-msvc/src/winapi/shared/ntdef.rs.html#120
|
||||
if ret != 0 {
|
||||
return int(ret), nil
|
||||
}
|
||||
}
|
||||
var systemInfo systemInfo
|
||||
_, _, err = procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
|
||||
if systemInfo.dwNumberOfProcessors == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return int(systemInfo.dwNumberOfProcessors), nil
|
||||
}
|
||||
// physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499
|
||||
// for the time being, try with unreliable and slow WMI call…
|
||||
var dst []Win32_Processor
|
||||
q := wmi.CreateQuery(&dst, "")
|
||||
if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var count uint32
|
||||
for _, d := range dst {
|
||||
count += d.NumberOfCores
|
||||
}
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.c
|
||||
#include <stdint.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include "disk_darwin.h"
|
||||
|
||||
#define IOKIT 1 /* to get io_name_t in device_types.h */
|
||||
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/storage/IOBlockStorageDriver.h>
|
||||
#include <IOKit/storage/IOMedia.h>
|
||||
#include <IOKit/IOBSD.h>
|
||||
|
||||
#include <mach/mach_host.h>
|
||||
|
||||
static int getdrivestat(io_registry_entry_t d, DriveStats *stat);
|
||||
static int fillstat(io_registry_entry_t d, DriveStats *stat);
|
||||
|
||||
int
|
||||
readdrivestat(DriveStats a[], int n)
|
||||
{
|
||||
mach_port_t port;
|
||||
CFMutableDictionaryRef match;
|
||||
io_iterator_t drives;
|
||||
io_registry_entry_t d;
|
||||
kern_return_t status;
|
||||
int na, rv;
|
||||
|
||||
IOMasterPort(bootstrap_port, &port);
|
||||
match = IOServiceMatching("IOMedia");
|
||||
CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
|
||||
status = IOServiceGetMatchingServices(port, match, &drives);
|
||||
if(status != KERN_SUCCESS)
|
||||
return -1;
|
||||
|
||||
na = 0;
|
||||
while(na < n && (d=IOIteratorNext(drives)) > 0){
|
||||
rv = getdrivestat(d, &a[na]);
|
||||
if(rv < 0)
|
||||
return -1;
|
||||
if(rv > 0)
|
||||
na++;
|
||||
IOObjectRelease(d);
|
||||
}
|
||||
IOObjectRelease(drives);
|
||||
return na;
|
||||
}
|
||||
|
||||
static int
|
||||
getdrivestat(io_registry_entry_t d, DriveStats *stat)
|
||||
{
|
||||
io_registry_entry_t parent;
|
||||
kern_return_t status;
|
||||
CFDictionaryRef props;
|
||||
CFStringRef name;
|
||||
CFNumberRef num;
|
||||
int rv;
|
||||
|
||||
memset(stat, 0, sizeof *stat);
|
||||
status = IORegistryEntryGetParentEntry(d, kIOServicePlane, &parent);
|
||||
if(status != KERN_SUCCESS)
|
||||
return -1;
|
||||
if(!IOObjectConformsTo(parent, "IOBlockStorageDriver")){
|
||||
IOObjectRelease(parent);
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
|
||||
if(status != KERN_SUCCESS){
|
||||
IOObjectRelease(parent);
|
||||
return -1;
|
||||
}
|
||||
name = (CFStringRef)CFDictionaryGetValue(props, CFSTR(kIOBSDNameKey));
|
||||
CFStringGetCString(name, stat->name, NAMELEN, CFStringGetSystemEncoding());
|
||||
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaSizeKey));
|
||||
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->size);
|
||||
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaPreferredBlockSizeKey));
|
||||
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->blocksize);
|
||||
CFRelease(props);
|
||||
|
||||
rv = fillstat(parent, stat);
|
||||
IOObjectRelease(parent);
|
||||
if(rv < 0)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct {
|
||||
char *key;
|
||||
size_t off;
|
||||
} statstab[] = {
|
||||
{kIOBlockStorageDriverStatisticsBytesReadKey, offsetof(DriveStats, read)},
|
||||
{kIOBlockStorageDriverStatisticsBytesWrittenKey, offsetof(DriveStats, written)},
|
||||
{kIOBlockStorageDriverStatisticsReadsKey, offsetof(DriveStats, nread)},
|
||||
{kIOBlockStorageDriverStatisticsWritesKey, offsetof(DriveStats, nwrite)},
|
||||
{kIOBlockStorageDriverStatisticsTotalReadTimeKey, offsetof(DriveStats, readtime)},
|
||||
{kIOBlockStorageDriverStatisticsTotalWriteTimeKey, offsetof(DriveStats, writetime)},
|
||||
{kIOBlockStorageDriverStatisticsLatentReadTimeKey, offsetof(DriveStats, readlat)},
|
||||
{kIOBlockStorageDriverStatisticsLatentWriteTimeKey, offsetof(DriveStats, writelat)},
|
||||
};
|
||||
|
||||
static int
|
||||
fillstat(io_registry_entry_t d, DriveStats *stat)
|
||||
{
|
||||
CFDictionaryRef props, v;
|
||||
CFNumberRef num;
|
||||
kern_return_t status;
|
||||
typeof(statstab[0]) *bp, *ep;
|
||||
|
||||
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
|
||||
if(status != KERN_SUCCESS)
|
||||
return -1;
|
||||
v = (CFDictionaryRef)CFDictionaryGetValue(props, CFSTR(kIOBlockStorageDriverStatisticsKey));
|
||||
if(v == NULL){
|
||||
CFRelease(props);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ep = &statstab[sizeof(statstab)/sizeof(statstab[0])];
|
||||
for(bp = &statstab[0]; bp < ep; bp++){
|
||||
CFStringRef s;
|
||||
|
||||
s = CFStringCreateWithCString(kCFAllocatorDefault, bp->key, CFStringGetSystemEncoding());
|
||||
num = (CFNumberRef)CFDictionaryGetValue(v, s);
|
||||
if(num)
|
||||
CFNumberGetValue(num, kCFNumberSInt64Type, ((char*)stat)+bp->off);
|
||||
CFRelease(s);
|
||||
}
|
||||
|
||||
CFRelease(props);
|
||||
return 0;
|
||||
}
|
||||
+28
-159
@@ -1,164 +1,33 @@
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/storage/IOBlockStorageDriver.h>
|
||||
#include <IOKit/storage/IOMedia.h>
|
||||
#include <IOKit/IOBSD.h>
|
||||
// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.h
|
||||
typedef struct DriveStats DriveStats;
|
||||
typedef struct CPUStats CPUStats;
|
||||
|
||||
// The iterator of all things disk. Allocated by StartIOCounterFetch, released
|
||||
// by EndIOCounterFetch.
|
||||
static io_iterator_t diskIter;
|
||||
enum {
|
||||
NDRIVE = 16,
|
||||
NAMELEN = 31
|
||||
};
|
||||
|
||||
// Begins fetching IO counters.
|
||||
//
|
||||
// Returns 1 if the fetch started successfully, false otherwise.
|
||||
//
|
||||
// If the fetch was started successfully, you must call EndIOCounterFetch once
|
||||
// done to release resources.
|
||||
int StartIOCounterFetch()
|
||||
{
|
||||
if (IOServiceGetMatchingServices(kIOMasterPortDefault,
|
||||
IOServiceMatching(kIOMediaClass),
|
||||
&diskIter) != kIOReturnSuccess) {
|
||||
return 0;
|
||||
}
|
||||
struct DriveStats {
|
||||
char name[NAMELEN+1];
|
||||
int64_t size;
|
||||
int64_t blocksize;
|
||||
|
||||
return 1;
|
||||
}
|
||||
int64_t read;
|
||||
int64_t written;
|
||||
int64_t nread;
|
||||
int64_t nwrite;
|
||||
int64_t readtime;
|
||||
int64_t writetime;
|
||||
int64_t readlat;
|
||||
int64_t writelat;
|
||||
};
|
||||
|
||||
// Releases resources from fetching IO counters.
|
||||
void EndIOCounterFetch()
|
||||
{
|
||||
IOObjectRelease(diskIter);
|
||||
}
|
||||
|
||||
// The current disk entry of interest. Allocated by FetchNextDisk(), released by
|
||||
// ReadDiskInfo().
|
||||
static io_registry_entry_t diskEntry;
|
||||
|
||||
// The parent of diskEntry. Same lifetimes.
|
||||
static io_registry_entry_t parentEntry;
|
||||
|
||||
// Fetches the next disk. Note that a disk entry is allocated, and will be held
|
||||
// until it is processed and freed by ReadDiskInfo.
|
||||
int FetchNextDisk()
|
||||
{
|
||||
while ((diskEntry = IOIteratorNext(diskIter)) != 0) {
|
||||
// We are iterating IOMedia. We need to get the parent too (IOBSD).
|
||||
if (IORegistryEntryGetParentEntry(diskEntry, kIOServicePlane, &parentEntry) != kIOReturnSuccess) {
|
||||
// something is wrong...
|
||||
IOObjectRelease(diskEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IOObjectConformsTo(parentEntry, "IOBlockStorageDriver")) {
|
||||
// no use to us, try the next disk
|
||||
IOObjectRelease(diskEntry);
|
||||
IOObjectRelease(parentEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Got a disk OK.
|
||||
return 1;
|
||||
}
|
||||
|
||||
// No more disks.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reads the current disk (from iteration) info into DiskInfo struct.
|
||||
// Once done, all resources from the current iteration of reading are freed,
|
||||
// ready for FetchNextDisk() to be called again.
|
||||
int ReadDiskInfo(DiskInfo *info)
|
||||
{
|
||||
// Parent props. Allocated by us.
|
||||
CFDictionaryRef parentProps = NULL;
|
||||
|
||||
// Disk props. Allocated by us.
|
||||
CFDictionaryRef diskProps = NULL;
|
||||
|
||||
// Disk stats, fetched by us, but not allocated by us.
|
||||
CFDictionaryRef stats = NULL;
|
||||
|
||||
if (IORegistryEntryCreateCFProperties(diskEntry, (CFMutableDictionaryRef *)&parentProps,
|
||||
kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
|
||||
{
|
||||
// can't get parent props, give up
|
||||
CFRelease(parentProps);
|
||||
IOObjectRelease(diskEntry);
|
||||
IOObjectRelease(parentEntry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (IORegistryEntryCreateCFProperties(parentEntry, (CFMutableDictionaryRef *)&diskProps,
|
||||
kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
|
||||
{
|
||||
// can't get disk props, give up
|
||||
CFRelease(parentProps);
|
||||
CFRelease(diskProps);
|
||||
IOObjectRelease(diskEntry);
|
||||
IOObjectRelease(parentEntry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start fetching
|
||||
CFStringRef cfDiskName = (CFStringRef)CFDictionaryGetValue(parentProps, CFSTR(kIOBSDNameKey));
|
||||
CFStringGetCString(cfDiskName, info->DiskName, MAX_DISK_NAME, CFStringGetSystemEncoding());
|
||||
stats = (CFDictionaryRef)CFDictionaryGetValue( diskProps, CFSTR(kIOBlockStorageDriverStatisticsKey));
|
||||
|
||||
if (stats == NULL) {
|
||||
// stat fetch failed...
|
||||
CFRelease(parentProps);
|
||||
CFRelease(diskProps);
|
||||
IOObjectRelease(parentEntry);
|
||||
IOObjectRelease(diskEntry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
CFNumberRef cfnum;
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->Reads);
|
||||
} else {
|
||||
info->Reads = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->Writes);
|
||||
} else {
|
||||
info->Writes = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->ReadBytes);
|
||||
} else {
|
||||
info->ReadBytes = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->WriteBytes);
|
||||
} else {
|
||||
info->WriteBytes = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->ReadTime);
|
||||
} else {
|
||||
info->ReadTime = 0;
|
||||
}
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->WriteTime);
|
||||
} else {
|
||||
info->WriteTime = 0;
|
||||
}
|
||||
|
||||
// note: read/write time are in ns, but we want ms.
|
||||
info->ReadTime = info->ReadTime / 1000 / 1000;
|
||||
info->WriteTime = info->WriteTime / 1000 / 1000;
|
||||
|
||||
CFRelease(parentProps);
|
||||
CFRelease(diskProps);
|
||||
IOObjectRelease(parentEntry);
|
||||
IOObjectRelease(diskEntry);
|
||||
return 0;
|
||||
}
|
||||
struct CPUStats {
|
||||
natural_t user;
|
||||
natural_t nice;
|
||||
natural_t sys;
|
||||
natural_t idle;
|
||||
};
|
||||
|
||||
extern int readdrivestat(DriveStats a[], int n);
|
||||
extern int readcpustat(CPUStats *cpu);
|
||||
|
||||
+15
-61
@@ -4,32 +4,15 @@
|
||||
package disk
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lobjc -framework Foundation -framework IOKit
|
||||
#cgo LDFLAGS: -framework CoreFoundation -framework IOKit
|
||||
#include <stdint.h>
|
||||
|
||||
// ### enough?
|
||||
const int MAX_DISK_NAME = 100;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char DiskName[MAX_DISK_NAME];
|
||||
int64_t Reads;
|
||||
int64_t Writes;
|
||||
int64_t ReadBytes;
|
||||
int64_t WriteBytes;
|
||||
int64_t ReadTime;
|
||||
int64_t WriteTime;
|
||||
} DiskInfo;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include "disk_darwin.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
@@ -39,57 +22,28 @@ func IOCounters(names ...string) (map[string]IOCountersStat, error) {
|
||||
}
|
||||
|
||||
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
|
||||
if C.StartIOCounterFetch() == 0 {
|
||||
return nil, errors.New("Unable to fetch disk list")
|
||||
var buf [C.NDRIVE]C.DriveStats
|
||||
n, err := C.readdrivestat(&buf[0], C.int(len(buf)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clean up when we are done.
|
||||
defer C.EndIOCounterFetch()
|
||||
ret := make(map[string]IOCountersStat, 0)
|
||||
|
||||
for {
|
||||
res := C.FetchNextDisk()
|
||||
if res == -1 {
|
||||
return nil, errors.New("Unable to fetch disk information")
|
||||
} else if res == 0 {
|
||||
break // done
|
||||
}
|
||||
|
||||
di := C.DiskInfo{}
|
||||
if C.ReadDiskInfo((*C.DiskInfo)(unsafe.Pointer(&di))) == -1 {
|
||||
return nil, errors.New("Unable to fetch disk properties")
|
||||
}
|
||||
|
||||
// Used to only get the necessary part of the C string.
|
||||
isRuneNull := func(r rune) bool {
|
||||
return r == '\u0000'
|
||||
}
|
||||
|
||||
// Map from the darwin-specific C struct to the Go type
|
||||
//
|
||||
// ### missing: IopsInProgress, WeightedIO, MergedReadCount,
|
||||
// MergedWriteCount, SerialNumber
|
||||
// IOKit can give us at least the serial number I think...
|
||||
for i := 0; i < int(n); i++ {
|
||||
d := IOCountersStat{
|
||||
// Note: The Go type wants unsigned values, but CFNumberGetValue
|
||||
// doesn't appear to be able to give us unsigned values. So, we
|
||||
// cast, and hope for the best.
|
||||
ReadBytes: uint64(di.ReadBytes),
|
||||
WriteBytes: uint64(di.WriteBytes),
|
||||
ReadCount: uint64(di.Reads),
|
||||
WriteCount: uint64(di.Writes),
|
||||
ReadTime: uint64(di.ReadTime),
|
||||
WriteTime: uint64(di.WriteTime),
|
||||
IoTime: uint64(di.ReadTime + di.WriteTime),
|
||||
Name: strings.TrimFunc(C.GoStringN(&di.DiskName[0], C.MAX_DISK_NAME), isRuneNull),
|
||||
ReadBytes: uint64(buf[i].read),
|
||||
WriteBytes: uint64(buf[i].written),
|
||||
ReadCount: uint64(buf[i].nread),
|
||||
WriteCount: uint64(buf[i].nwrite),
|
||||
ReadTime: uint64(buf[i].readtime / 1000 / 1000), // note: read/write time are in ns, but we want ms.
|
||||
WriteTime: uint64(buf[i].writetime / 1000 / 1000),
|
||||
IoTime: uint64((buf[i].readtime + buf[i].writetime) / 1000 / 1000),
|
||||
Name: C.GoString(&buf[i].name[0]),
|
||||
}
|
||||
|
||||
if len(names) > 0 && !common.StringsHas(names, d.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
ret[d.Name] = d
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
+22
-22
@@ -23,65 +23,65 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
|
||||
var ret []PartitionStat
|
||||
|
||||
// get length
|
||||
count, err := unix.Getfsstat(nil, MNT_WAIT)
|
||||
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
fs := make([]Statfs, count)
|
||||
if _, err = Getfsstat(fs, MNT_WAIT); err != nil {
|
||||
if _, err = Getfsstat(fs, unix.MNT_WAIT); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
for _, stat := range fs {
|
||||
opts := "rw"
|
||||
if stat.Flags&MNT_RDONLY != 0 {
|
||||
if stat.Flags&unix.MNT_RDONLY != 0 {
|
||||
opts = "ro"
|
||||
}
|
||||
if stat.Flags&MNT_SYNCHRONOUS != 0 {
|
||||
if stat.Flags&unix.MNT_SYNCHRONOUS != 0 {
|
||||
opts += ",sync"
|
||||
}
|
||||
if stat.Flags&MNT_NOEXEC != 0 {
|
||||
if stat.Flags&unix.MNT_NOEXEC != 0 {
|
||||
opts += ",noexec"
|
||||
}
|
||||
if stat.Flags&MNT_NOSUID != 0 {
|
||||
if stat.Flags&unix.MNT_NOSUID != 0 {
|
||||
opts += ",nosuid"
|
||||
}
|
||||
if stat.Flags&MNT_UNION != 0 {
|
||||
if stat.Flags&unix.MNT_UNION != 0 {
|
||||
opts += ",union"
|
||||
}
|
||||
if stat.Flags&MNT_ASYNC != 0 {
|
||||
if stat.Flags&unix.MNT_ASYNC != 0 {
|
||||
opts += ",async"
|
||||
}
|
||||
if stat.Flags&MNT_SUIDDIR != 0 {
|
||||
if stat.Flags&unix.MNT_SUIDDIR != 0 {
|
||||
opts += ",suiddir"
|
||||
}
|
||||
if stat.Flags&MNT_SOFTDEP != 0 {
|
||||
if stat.Flags&unix.MNT_SOFTDEP != 0 {
|
||||
opts += ",softdep"
|
||||
}
|
||||
if stat.Flags&MNT_NOSYMFOLLOW != 0 {
|
||||
if stat.Flags&unix.MNT_NOSYMFOLLOW != 0 {
|
||||
opts += ",nosymfollow"
|
||||
}
|
||||
if stat.Flags&MNT_GJOURNAL != 0 {
|
||||
opts += ",gjounalc"
|
||||
if stat.Flags&unix.MNT_GJOURNAL != 0 {
|
||||
opts += ",gjournal"
|
||||
}
|
||||
if stat.Flags&MNT_MULTILABEL != 0 {
|
||||
if stat.Flags&unix.MNT_MULTILABEL != 0 {
|
||||
opts += ",multilabel"
|
||||
}
|
||||
if stat.Flags&MNT_ACLS != 0 {
|
||||
if stat.Flags&unix.MNT_ACLS != 0 {
|
||||
opts += ",acls"
|
||||
}
|
||||
if stat.Flags&MNT_NOATIME != 0 {
|
||||
opts += ",noattime"
|
||||
if stat.Flags&unix.MNT_NOATIME != 0 {
|
||||
opts += ",noatime"
|
||||
}
|
||||
if stat.Flags&MNT_NOCLUSTERR != 0 {
|
||||
opts += ",nocluster"
|
||||
if stat.Flags&unix.MNT_NOCLUSTERR != 0 {
|
||||
opts += ",noclusterr"
|
||||
}
|
||||
if stat.Flags&MNT_NOCLUSTERW != 0 {
|
||||
if stat.Flags&unix.MNT_NOCLUSTERW != 0 {
|
||||
opts += ",noclusterw"
|
||||
}
|
||||
if stat.Flags&MNT_NFS4ACLS != 0 {
|
||||
opts += ",nfs4acls"
|
||||
if stat.Flags&unix.MNT_NFS4ACLS != 0 {
|
||||
opts += ",nfsv4acls"
|
||||
}
|
||||
|
||||
d := PartitionStat{
|
||||
|
||||
-22
@@ -15,28 +15,6 @@ const (
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
|
||||
MNT_RDONLY = 0x00000001
|
||||
MNT_SYNCHRONOUS = 0x00000002
|
||||
MNT_NOEXEC = 0x00000004
|
||||
MNT_NOSUID = 0x00000008
|
||||
MNT_UNION = 0x00000020
|
||||
MNT_ASYNC = 0x00000040
|
||||
MNT_SUIDDIR = 0x00100000
|
||||
MNT_SOFTDEP = 0x00200000
|
||||
MNT_NOSYMFOLLOW = 0x00400000
|
||||
MNT_GJOURNAL = 0x02000000
|
||||
MNT_MULTILABEL = 0x04000000
|
||||
MNT_ACLS = 0x08000000
|
||||
MNT_NOATIME = 0x10000000
|
||||
MNT_NOCLUSTERR = 0x40000000
|
||||
MNT_NOCLUSTERW = 0x80000000
|
||||
MNT_NFS4ACLS = 0x00000010
|
||||
|
||||
MNT_WAIT = 1
|
||||
MNT_NOWAIT = 2
|
||||
MNT_LAZY = 3
|
||||
MNT_SUSPEND = 4
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
-22
@@ -15,28 +15,6 @@ const (
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
|
||||
MNT_RDONLY = 0x00000001
|
||||
MNT_SYNCHRONOUS = 0x00000002
|
||||
MNT_NOEXEC = 0x00000004
|
||||
MNT_NOSUID = 0x00000008
|
||||
MNT_UNION = 0x00000020
|
||||
MNT_ASYNC = 0x00000040
|
||||
MNT_SUIDDIR = 0x00100000
|
||||
MNT_SOFTDEP = 0x00200000
|
||||
MNT_NOSYMFOLLOW = 0x00400000
|
||||
MNT_GJOURNAL = 0x02000000
|
||||
MNT_MULTILABEL = 0x04000000
|
||||
MNT_ACLS = 0x08000000
|
||||
MNT_NOATIME = 0x10000000
|
||||
MNT_NOCLUSTERR = 0x40000000
|
||||
MNT_NOCLUSTERW = 0x80000000
|
||||
MNT_NFS4ACLS = 0x00000010
|
||||
|
||||
MNT_WAIT = 1
|
||||
MNT_NOWAIT = 2
|
||||
MNT_LAZY = 3
|
||||
MNT_SUSPEND = 4
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package disk
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x4
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x4
|
||||
sizeofLongLong = 0x8
|
||||
sizeofLongDouble = 0x8
|
||||
|
||||
DEVSTAT_NO_DATA = 0x00
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfDevstat = 0xf0
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int32
|
||||
_C_long_long int64
|
||||
_C_long_double int64
|
||||
)
|
||||
|
||||
type Statfs struct {
|
||||
Version uint32
|
||||
Type uint32
|
||||
Flags uint64
|
||||
Bsize uint64
|
||||
Iosize uint64
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail int64
|
||||
Files uint64
|
||||
Ffree int64
|
||||
Syncwrites uint64
|
||||
Asyncwrites uint64
|
||||
Syncreads uint64
|
||||
Asyncreads uint64
|
||||
Spare [10]uint64
|
||||
Namemax uint32
|
||||
Owner uint32
|
||||
Fsid Fsid
|
||||
Charspare [80]int8
|
||||
Fstypename [16]int8
|
||||
Mntfromname [88]int8
|
||||
Mntonname [88]int8
|
||||
}
|
||||
type Fsid struct {
|
||||
Val [2]int32
|
||||
}
|
||||
|
||||
type Devstat struct {
|
||||
Sequence0 uint32
|
||||
Allocated int32
|
||||
Start_count uint32
|
||||
End_count uint32
|
||||
Busy_from Bintime
|
||||
Dev_links _Ctype_struct___0
|
||||
Device_number uint32
|
||||
Device_name [16]int8
|
||||
Unit_number int32
|
||||
Bytes [4]uint64
|
||||
Operations [4]uint64
|
||||
Duration [4]Bintime
|
||||
Busy_time Bintime
|
||||
Creation_time Bintime
|
||||
Block_size uint32
|
||||
Tag_types [3]uint64
|
||||
Flags uint32
|
||||
Device_type uint32
|
||||
Priority uint32
|
||||
Id *byte
|
||||
Sequence1 uint32
|
||||
}
|
||||
type Bintime struct {
|
||||
Sec int32
|
||||
Frac uint64
|
||||
}
|
||||
|
||||
type _Ctype_struct___0 struct {
|
||||
Empty uint32
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// +build freebsd
|
||||
// +build arm64
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs disk/types_freebsd.go
|
||||
|
||||
package disk
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
sizeofLongDouble = 0x8
|
||||
|
||||
DEVSTAT_NO_DATA = 0x00
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfDevstat = 0x120
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
_C_long_double int64
|
||||
)
|
||||
|
||||
type Statfs struct {
|
||||
Version uint32
|
||||
Type uint32
|
||||
Flags uint64
|
||||
Bsize uint64
|
||||
Iosize uint64
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail int64
|
||||
Files uint64
|
||||
Ffree int64
|
||||
Syncwrites uint64
|
||||
Asyncwrites uint64
|
||||
Syncreads uint64
|
||||
Asyncreads uint64
|
||||
Spare [10]uint64
|
||||
Namemax uint32
|
||||
Owner uint32
|
||||
Fsid Fsid
|
||||
Charspare [80]uint8
|
||||
Fstypename [16]int8
|
||||
Mntfromname [1024]int8
|
||||
Mntonname [1024]int8
|
||||
}
|
||||
type Fsid struct {
|
||||
Val [2]int32
|
||||
}
|
||||
|
||||
type Devstat struct {
|
||||
Sequence0 uint32
|
||||
Allocated int32
|
||||
Start_count uint32
|
||||
End_count uint32
|
||||
Busy_from Bintime
|
||||
Dev_links _Ctype_struct___0
|
||||
Device_number uint32
|
||||
Device_name [16]int8
|
||||
Unit_number int32
|
||||
Bytes [4]uint64
|
||||
Operations [4]uint64
|
||||
Duration [4]Bintime
|
||||
Busy_time Bintime
|
||||
Creation_time Bintime
|
||||
Block_size uint32
|
||||
Tag_types [3]uint64
|
||||
Flags uint32
|
||||
Device_type uint32
|
||||
Priority uint32
|
||||
Id *byte
|
||||
Sequence1 uint32
|
||||
Pad_cgo_0 [4]byte
|
||||
}
|
||||
type Bintime struct {
|
||||
Sec int64
|
||||
Frac uint64
|
||||
}
|
||||
|
||||
type _Ctype_struct___0 struct {
|
||||
Empty uint64
|
||||
}
|
||||
+82
-15
@@ -8,13 +8,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,6 +47,7 @@ const (
|
||||
FUSE_SUPER_MAGIC = 0x65735546
|
||||
FUTEXFS_SUPER_MAGIC = 0xBAD1DEA
|
||||
HFS_SUPER_MAGIC = 0x4244
|
||||
HFSPLUS_SUPER_MAGIC = 0x482b
|
||||
HOSTFS_SUPER_MAGIC = 0x00c0ffee
|
||||
HPFS_SUPER_MAGIC = 0xF995E849
|
||||
HUGETLBFS_MAGIC = 0x958458f6
|
||||
@@ -156,6 +157,7 @@ var fsTypeMap = map[int64]string{
|
||||
GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */
|
||||
GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */
|
||||
HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */
|
||||
HFSPLUS_SUPER_MAGIC: "hfsplus", /* 0x482b local */
|
||||
HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */
|
||||
HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */
|
||||
MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */
|
||||
@@ -224,30 +226,95 @@ func Partitions(all bool) ([]PartitionStat, error) {
|
||||
}
|
||||
|
||||
func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
|
||||
filename := common.HostProc("self/mounts")
|
||||
useMounts := false
|
||||
|
||||
filename := common.HostProc("self/mountinfo")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err != err.(*os.PathError) {
|
||||
return nil, err
|
||||
}
|
||||
// if kernel does not support self/mountinfo, fallback to self/mounts (<2.6.26)
|
||||
useMounts = true
|
||||
filename = common.HostProc("self/mounts")
|
||||
lines, err = common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fs, err := getFileSystems()
|
||||
if err != nil {
|
||||
if err != nil && !all {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]PartitionStat, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
d := PartitionStat{
|
||||
Device: fields[0],
|
||||
Mountpoint: unescapeFstab(fields[1]),
|
||||
Fstype: fields[2],
|
||||
Opts: fields[3],
|
||||
}
|
||||
if all == false {
|
||||
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
|
||||
continue
|
||||
var d PartitionStat
|
||||
if useMounts {
|
||||
fields := strings.Fields(line)
|
||||
|
||||
d = PartitionStat{
|
||||
Device: fields[0],
|
||||
Mountpoint: unescapeFstab(fields[1]),
|
||||
Fstype: fields[2],
|
||||
Opts: fields[3],
|
||||
}
|
||||
|
||||
if !all {
|
||||
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// a line of self/mountinfo has the following structure:
|
||||
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
|
||||
// (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11)
|
||||
|
||||
// split the mountinfo line by the separator hyphen
|
||||
parts := strings.Split(line, " - ")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("found invalid mountinfo line in file %s: %s ", filename, line)
|
||||
}
|
||||
|
||||
fields := strings.Fields(parts[0])
|
||||
blockDeviceID := fields[2]
|
||||
mountPoint := fields[4]
|
||||
mountOpts := fields[5]
|
||||
|
||||
fields = strings.Fields(parts[1])
|
||||
fstype := fields[0]
|
||||
device := fields[1]
|
||||
|
||||
d = PartitionStat{
|
||||
Device: device,
|
||||
Mountpoint: unescapeFstab(mountPoint),
|
||||
Fstype: fstype,
|
||||
Opts: mountOpts,
|
||||
}
|
||||
|
||||
if !all {
|
||||
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(d.Device, "/dev/mapper/") {
|
||||
devpath, err := filepath.EvalSymlinks(d.Device)
|
||||
if err == nil {
|
||||
d.Device = devpath
|
||||
}
|
||||
}
|
||||
|
||||
// /dev/root is not the real device name
|
||||
// so we get the real device name from its major/minor number
|
||||
if d.Device == "/dev/root" {
|
||||
devpath, err := os.Readlink(common.HostSys("/dev/block/" + blockDeviceID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Device = strings.Replace(d.Device, "root", filepath.Base(devpath), 1)
|
||||
}
|
||||
}
|
||||
ret = append(ret, d)
|
||||
|
||||
+17
-8
@@ -21,36 +21,45 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
|
||||
var ret []PartitionStat
|
||||
|
||||
// get length
|
||||
count, err := unix.Getfsstat(nil, MNT_WAIT)
|
||||
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
fs := make([]Statfs, count)
|
||||
if _, err = Getfsstat(fs, MNT_WAIT); err != nil {
|
||||
if _, err = Getfsstat(fs, unix.MNT_WAIT); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
for _, stat := range fs {
|
||||
opts := "rw"
|
||||
if stat.F_flags&MNT_RDONLY != 0 {
|
||||
if stat.F_flags&unix.MNT_RDONLY != 0 {
|
||||
opts = "ro"
|
||||
}
|
||||
if stat.F_flags&MNT_SYNCHRONOUS != 0 {
|
||||
if stat.F_flags&unix.MNT_SYNCHRONOUS != 0 {
|
||||
opts += ",sync"
|
||||
}
|
||||
if stat.F_flags&MNT_NOEXEC != 0 {
|
||||
if stat.F_flags&unix.MNT_NOEXEC != 0 {
|
||||
opts += ",noexec"
|
||||
}
|
||||
if stat.F_flags&MNT_NOSUID != 0 {
|
||||
if stat.F_flags&unix.MNT_NOSUID != 0 {
|
||||
opts += ",nosuid"
|
||||
}
|
||||
if stat.F_flags&MNT_NODEV != 0 {
|
||||
if stat.F_flags&unix.MNT_NODEV != 0 {
|
||||
opts += ",nodev"
|
||||
}
|
||||
if stat.F_flags&MNT_ASYNC != 0 {
|
||||
if stat.F_flags&unix.MNT_ASYNC != 0 {
|
||||
opts += ",async"
|
||||
}
|
||||
if stat.F_flags&unix.MNT_SOFTDEP != 0 {
|
||||
opts += ",softdep"
|
||||
}
|
||||
if stat.F_flags&unix.MNT_NOATIME != 0 {
|
||||
opts += ",noatime"
|
||||
}
|
||||
if stat.F_flags&unix.MNT_WXALLOWED != 0 {
|
||||
opts += ",wxallowed"
|
||||
}
|
||||
|
||||
d := PartitionStat{
|
||||
Device: common.IntToString(stat.F_mntfromname[:]),
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs types_openbsd.go
|
||||
|
||||
package disk
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x4
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x4
|
||||
sizeofLongLong = 0x8
|
||||
sizeofLongDouble = 0x8
|
||||
|
||||
DEVSTAT_NO_DATA = 0x00
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfDiskstats = 0x60
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int32
|
||||
_C_long_long int64
|
||||
_C_long_double int64
|
||||
)
|
||||
|
||||
type Statfs struct {
|
||||
F_flags uint32
|
||||
F_bsize uint32
|
||||
F_iosize uint32
|
||||
F_blocks uint64
|
||||
F_bfree uint64
|
||||
F_bavail int64
|
||||
F_files uint64
|
||||
F_ffree uint64
|
||||
F_favail int64
|
||||
F_syncwrites uint64
|
||||
F_syncreads uint64
|
||||
F_asyncwrites uint64
|
||||
F_asyncreads uint64
|
||||
F_fsid Fsid
|
||||
F_namemax uint32
|
||||
F_owner uint32
|
||||
F_ctime uint64
|
||||
F_fstypename [16]int8
|
||||
F_mntonname [90]int8
|
||||
F_mntfromname [90]int8
|
||||
F_mntfromspec [90]int8
|
||||
Pad_cgo_0 [2]byte
|
||||
Mount_info [160]byte
|
||||
}
|
||||
type Diskstats struct {
|
||||
Name [16]int8
|
||||
Busy int32
|
||||
Rxfer uint64
|
||||
Wxfer uint64
|
||||
Seek uint64
|
||||
Rbytes uint64
|
||||
Wbytes uint64
|
||||
Attachtime Timeval
|
||||
Timestamp Timeval
|
||||
Time Timeval
|
||||
}
|
||||
type Fsid struct {
|
||||
Val [2]int32
|
||||
}
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int32
|
||||
}
|
||||
|
||||
type Diskstat struct{}
|
||||
type Bintime struct{}
|
||||
-11
@@ -15,17 +15,6 @@ const (
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
|
||||
MNT_RDONLY = 0x00000001
|
||||
MNT_SYNCHRONOUS = 0x00000002
|
||||
MNT_NOEXEC = 0x00000004
|
||||
MNT_NOSUID = 0x00000008
|
||||
MNT_NODEV = 0x00000010
|
||||
MNT_ASYNC = 0x00000040
|
||||
|
||||
MNT_WAIT = 1
|
||||
MNT_NOWAIT = 2
|
||||
MNT_LAZY = 3
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Usage returns a file system usage. path is a filessytem path such
|
||||
// Usage returns a file system usage. path is a filesystem path such
|
||||
// as "/", not device file path like "/dev/vda1". If you want to use
|
||||
// a return value of disk.Partitions, use "Mountpoint" not "Device".
|
||||
func Usage(path string) (*UsageStat, error) {
|
||||
|
||||
+1
-3
@@ -40,8 +40,6 @@ func Usage(path string) (*UsageStat, error) {
|
||||
}
|
||||
|
||||
func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
|
||||
ret := &UsageStat{}
|
||||
|
||||
lpFreeBytesAvailable := int64(0)
|
||||
lpTotalNumberOfBytes := int64(0)
|
||||
lpTotalNumberOfFreeBytes := int64(0)
|
||||
@@ -53,7 +51,7 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
|
||||
if diskret == 0 {
|
||||
return nil, err
|
||||
}
|
||||
ret = &UsageStat{
|
||||
ret := &UsageStat{
|
||||
Path: path,
|
||||
Total: uint64(lpTotalNumberOfBytes),
|
||||
Free: uint64(lpTotalNumberOfFreeBytes),
|
||||
|
||||
-23
@@ -42,29 +42,6 @@ const (
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
|
||||
// from sys/mount.h
|
||||
MNT_RDONLY = 0x00000001 /* read only filesystem */
|
||||
MNT_SYNCHRONOUS = 0x00000002 /* filesystem written synchronously */
|
||||
MNT_NOEXEC = 0x00000004 /* can't exec from filesystem */
|
||||
MNT_NOSUID = 0x00000008 /* don't honor setuid bits on fs */
|
||||
MNT_UNION = 0x00000020 /* union with underlying filesystem */
|
||||
MNT_ASYNC = 0x00000040 /* filesystem written asynchronously */
|
||||
MNT_SUIDDIR = 0x00100000 /* special handling of SUID on dirs */
|
||||
MNT_SOFTDEP = 0x00200000 /* soft updates being done */
|
||||
MNT_NOSYMFOLLOW = 0x00400000 /* do not follow symlinks */
|
||||
MNT_GJOURNAL = 0x02000000 /* GEOM journal support enabled */
|
||||
MNT_MULTILABEL = 0x04000000 /* MAC support for individual objects */
|
||||
MNT_ACLS = 0x08000000 /* ACL support enabled */
|
||||
MNT_NOATIME = 0x10000000 /* disable update of file access time */
|
||||
MNT_NOCLUSTERR = 0x40000000 /* disable cluster read */
|
||||
MNT_NOCLUSTERW = 0x80000000 /* disable cluster write */
|
||||
MNT_NFS4ACLS = 0x00000010
|
||||
|
||||
MNT_WAIT = 1 /* synchronously wait for I/O to complete */
|
||||
MNT_NOWAIT = 2 /* start all I/O, but do not wait for it */
|
||||
MNT_LAZY = 3 /* push data not written by filesystem syncer */
|
||||
MNT_SUSPEND = 4 /* Suspend file system after sync */
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
-12
@@ -33,18 +33,6 @@ const (
|
||||
DEVSTAT_READ = 0x01
|
||||
DEVSTAT_WRITE = 0x02
|
||||
DEVSTAT_FREE = 0x03
|
||||
|
||||
// from sys/mount.h
|
||||
MNT_RDONLY = 0x00000001 /* read only filesystem */
|
||||
MNT_SYNCHRONOUS = 0x00000002 /* filesystem written synchronously */
|
||||
MNT_NOEXEC = 0x00000004 /* can't exec from filesystem */
|
||||
MNT_NOSUID = 0x00000008 /* don't honor setuid bits on fs */
|
||||
MNT_NODEV = 0x00000010 /* don't interpret special files */
|
||||
MNT_ASYNC = 0x00000040 /* filesystem written asynchronously */
|
||||
|
||||
MNT_WAIT = 1 /* synchronously wait for I/O to complete */
|
||||
MNT_NOWAIT = 2 /* start all I/O, but do not wait for it */
|
||||
MNT_LAZY = 3 /* push data not written by filesystem syncer */
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ type InfoStat struct {
|
||||
PlatformFamily string `json:"platformFamily"` // ex: debian, rhel
|
||||
PlatformVersion string `json:"platformVersion"` // version of the complete OS
|
||||
KernelVersion string `json:"kernelVersion"` // version of the OS kernel (if available)
|
||||
KernelArch string `json:"kernelArch"` // native cpu architecture queried at runtime, as returned by `uname -m` or empty string in case of error
|
||||
VirtualizationSystem string `json:"virtualizationSystem"`
|
||||
VirtualizationRole string `json:"virtualizationRole"` // guest or host
|
||||
HostID string `json:"hostid"` // ex: uuid
|
||||
|
||||
+33
-40
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// from utmpx.h
|
||||
@@ -43,6 +43,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
ret.KernelVersion = kernelVersion
|
||||
}
|
||||
|
||||
kernelArch, err := kernelArch()
|
||||
if err == nil {
|
||||
ret.KernelArch = kernelArch
|
||||
}
|
||||
|
||||
platform, family, pver, err := PlatformInformation()
|
||||
if err == nil {
|
||||
ret.Platform = platform
|
||||
@@ -67,9 +72,9 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
ret.Procs = uint64(len(procs))
|
||||
}
|
||||
|
||||
values, err := common.DoSysctrlWithContext(ctx, "kern.uuid")
|
||||
if err == nil && len(values) == 1 && values[0] != "" {
|
||||
ret.HostID = strings.ToLower(values[0])
|
||||
uuid, err := unix.Sysctl("kern.uuid")
|
||||
if err == nil && uuid != "" {
|
||||
ret.HostID = strings.ToLower(uuid)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
@@ -83,24 +88,22 @@ func BootTime() (uint64, error) {
|
||||
}
|
||||
|
||||
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
||||
// https://github.com/AaronO/dashd/blob/222e32ef9f7a1f9bea4a8da2c3627c4cb992f860/probe/probe_darwin.go
|
||||
t := atomic.LoadUint64(&cachedBootTime)
|
||||
if t != 0 {
|
||||
return t, nil
|
||||
}
|
||||
values, err := common.DoSysctrlWithContext(ctx, "kern.boottime")
|
||||
value, err := unix.Sysctl("kern.boottime")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// ex: { sec = 1392261637, usec = 627534 } Thu Feb 13 12:20:37 2014
|
||||
v := strings.Replace(values[2], ",", "", 1)
|
||||
boottime, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
t = uint64(boottime)
|
||||
atomic.StoreUint64(&cachedBootTime, t)
|
||||
bytes := []byte(value[:])
|
||||
var boottime uint64
|
||||
boottime = uint64(bytes[0]) + uint64(bytes[1])*256 + uint64(bytes[2])*256*256 + uint64(bytes[3])*256*256*256
|
||||
|
||||
return t, nil
|
||||
atomic.StoreUint64(&cachedBootTime, boottime)
|
||||
|
||||
return boottime, nil
|
||||
}
|
||||
|
||||
func uptime(boot uint64) uint64 {
|
||||
@@ -112,7 +115,7 @@ func Uptime() (uint64, error) {
|
||||
}
|
||||
|
||||
func UptimeWithContext(ctx context.Context) (uint64, error) {
|
||||
boot, err := BootTime()
|
||||
boot, err := BootTimeWithContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -180,21 +183,27 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
uname, err := exec.LookPath("uname")
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
out, err := invoke.CommandWithContext(ctx, uname, "-s")
|
||||
p, err := unix.Sysctl("kern.ostype")
|
||||
if err == nil {
|
||||
platform = strings.ToLower(strings.TrimSpace(string(out)))
|
||||
platform = strings.ToLower(p)
|
||||
}
|
||||
|
||||
out, err = invoke.CommandWithContext(ctx, sw_vers, "-productVersion")
|
||||
out, err := invoke.CommandWithContext(ctx, sw_vers, "-productVersion")
|
||||
if err == nil {
|
||||
pver = strings.ToLower(strings.TrimSpace(string(out)))
|
||||
}
|
||||
|
||||
// check if the macos server version file exists
|
||||
_, err = os.Stat("/System/Library/CoreServices/ServerVersion.plist")
|
||||
|
||||
// server file doesn't exist
|
||||
if os.IsNotExist(err) {
|
||||
family = "Standalone Workstation"
|
||||
} else {
|
||||
family = "Server"
|
||||
}
|
||||
|
||||
return platform, family, pver, nil
|
||||
}
|
||||
|
||||
@@ -211,22 +220,6 @@ func KernelVersion() (string, error) {
|
||||
}
|
||||
|
||||
func KernelVersionWithContext(ctx context.Context) (string, error) {
|
||||
uname, err := exec.LookPath("uname")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, uname, "-r")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
version := strings.ToLower(strings.TrimSpace(string(out)))
|
||||
return version, err
|
||||
}
|
||||
|
||||
func SensorsTemperatures() ([]TemperatureStat, error) {
|
||||
return SensorsTemperaturesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
return []TemperatureStat{}, common.ErrNotImplementedError
|
||||
version, err := unix.Sysctl("kern.osrelease")
|
||||
return strings.ToLower(version), err
|
||||
}
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// +build darwin
|
||||
// +build cgo
|
||||
|
||||
package host
|
||||
|
||||
// #cgo LDFLAGS: -framework IOKit
|
||||
// #include <stdio.h>
|
||||
// #include <string.h>
|
||||
// #include "include/smc.c"
|
||||
import "C"
|
||||
import "context"
|
||||
|
||||
func SensorsTemperatures() ([]TemperatureStat, error) {
|
||||
return SensorsTemperaturesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
temperatureKeys := []string{
|
||||
C.AMBIENT_AIR_0,
|
||||
C.AMBIENT_AIR_1,
|
||||
C.CPU_0_DIODE,
|
||||
C.CPU_0_HEATSINK,
|
||||
C.CPU_0_PROXIMITY,
|
||||
C.ENCLOSURE_BASE_0,
|
||||
C.ENCLOSURE_BASE_1,
|
||||
C.ENCLOSURE_BASE_2,
|
||||
C.ENCLOSURE_BASE_3,
|
||||
C.GPU_0_DIODE,
|
||||
C.GPU_0_HEATSINK,
|
||||
C.GPU_0_PROXIMITY,
|
||||
C.HARD_DRIVE_BAY,
|
||||
C.MEMORY_SLOT_0,
|
||||
C.MEMORY_SLOTS_PROXIMITY,
|
||||
C.NORTHBRIDGE,
|
||||
C.NORTHBRIDGE_DIODE,
|
||||
C.NORTHBRIDGE_PROXIMITY,
|
||||
C.THUNDERBOLT_0,
|
||||
C.THUNDERBOLT_1,
|
||||
C.WIRELESS_MODULE,
|
||||
}
|
||||
var temperatures []TemperatureStat
|
||||
|
||||
C.open_smc()
|
||||
defer C.close_smc()
|
||||
|
||||
for _, key := range temperatureKeys {
|
||||
temperatures = append(temperatures, TemperatureStat{
|
||||
SensorKey: key,
|
||||
Temperature: float64(C.get_temperature(C.CString(key))),
|
||||
})
|
||||
}
|
||||
return temperatures, nil
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// +build darwin
|
||||
// +build !cgo
|
||||
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
|
||||
func SensorsTemperatures() ([]TemperatureStat, error) {
|
||||
return SensorsTemperaturesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
return []TemperatureStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
+8
-2
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -49,6 +50,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
ret.KernelVersion = version
|
||||
}
|
||||
|
||||
kernelArch, err := kernelArch()
|
||||
if err == nil {
|
||||
ret.KernelArch = kernelArch
|
||||
}
|
||||
|
||||
system, role, err := Virtualization()
|
||||
if err == nil {
|
||||
ret.VirtualizationSystem = system
|
||||
@@ -143,11 +149,11 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
|
||||
b := buf[i*sizeOfUtmpx : (i+1)*sizeOfUtmpx]
|
||||
var u Utmpx
|
||||
br := bytes.NewReader(b)
|
||||
err := binary.Read(br, binary.LittleEndian, &u)
|
||||
err := binary.Read(br, binary.BigEndian, &u)
|
||||
if err != nil || u.Type != 4 {
|
||||
continue
|
||||
}
|
||||
sec := (binary.LittleEndian.Uint32(u.Tv.Sec[:])) / 2 // TODO:
|
||||
sec := math.Floor(float64(u.Tv) / 1000000)
|
||||
user := UserStat{
|
||||
User: common.IntToString(u.User[:]),
|
||||
Terminal: common.IntToString(u.Line[:]),
|
||||
|
||||
+6
-12
@@ -1,4 +1,4 @@
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package host
|
||||
@@ -9,7 +9,7 @@ const (
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x4
|
||||
sizeofLongLong = 0x8
|
||||
sizeOfUtmpx = 197 // TODO why should 197
|
||||
sizeOfUtmpx = 0xc5
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -27,17 +27,11 @@ type Utmp struct {
|
||||
}
|
||||
|
||||
type Utmpx struct {
|
||||
Type int16
|
||||
Tv Timeval
|
||||
Type uint8
|
||||
Tv uint64
|
||||
Id [8]int8
|
||||
Pid int32
|
||||
Pid uint32
|
||||
User [32]int8
|
||||
Line [16]int8
|
||||
Host [125]int8
|
||||
// X__ut_spare [64]int8
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec [4]byte
|
||||
Usec [3]byte
|
||||
Host [128]int8
|
||||
}
|
||||
|
||||
+6
-13
@@ -1,4 +1,4 @@
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package host
|
||||
@@ -9,7 +9,7 @@ const (
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
sizeOfUtmpx = 197 // TODO: why should 197, not 0x118
|
||||
sizeOfUtmpx = 0xc5
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -27,18 +27,11 @@ type Utmp struct {
|
||||
}
|
||||
|
||||
type Utmpx struct {
|
||||
Type int16
|
||||
Tv Timeval
|
||||
Type uint8
|
||||
Tv uint64
|
||||
Id [8]int8
|
||||
Pid int32
|
||||
Pid uint32
|
||||
User [32]int8
|
||||
Line [16]int8
|
||||
Host [125]int8
|
||||
// Host [128]int8
|
||||
// X__ut_spare [64]int8
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec [4]byte
|
||||
Usec [3]byte
|
||||
Host [128]int8
|
||||
}
|
||||
|
||||
+6
-13
@@ -1,4 +1,4 @@
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package host
|
||||
@@ -9,7 +9,7 @@ const (
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
sizeOfUtmpx = 197 // TODO: why should 197, not 0x118
|
||||
sizeOfUtmpx = 0xc5
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -27,18 +27,11 @@ type Utmp struct {
|
||||
}
|
||||
|
||||
type Utmpx struct {
|
||||
Type int16
|
||||
Tv Timeval
|
||||
Type uint8
|
||||
Tv uint64
|
||||
Id [8]int8
|
||||
Pid int32
|
||||
Pid uint32
|
||||
User [32]int8
|
||||
Line [16]int8
|
||||
Host [125]int8
|
||||
// Host [128]int8
|
||||
// X__ut_spare [64]int8
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec [4]byte
|
||||
Usec [3]byte
|
||||
Host [128]int8
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// +build freebsd
|
||||
// +build arm64
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs host/types_freebsd.go
|
||||
|
||||
package host
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
sizeOfUtmpx = 0xc5
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Utmp struct {
|
||||
Line [8]int8
|
||||
Name [16]int8
|
||||
Host [16]int8
|
||||
Time int32
|
||||
}
|
||||
|
||||
type Utmpx struct {
|
||||
Type uint8
|
||||
Tv uint64
|
||||
Id [8]int8
|
||||
Pid uint32
|
||||
User [32]int8
|
||||
Line [16]int8
|
||||
Host [128]int8
|
||||
}
|
||||
+30
-202
@@ -15,10 +15,10 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type LSB struct {
|
||||
@@ -56,6 +56,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
ret.KernelVersion = kernelVersion
|
||||
}
|
||||
|
||||
kernelArch, err := kernelArch()
|
||||
if err == nil {
|
||||
ret.KernelArch = kernelArch
|
||||
}
|
||||
|
||||
system, role, err := Virtualization()
|
||||
if err == nil {
|
||||
ret.VirtualizationSystem = system
|
||||
@@ -74,6 +79,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
|
||||
sysProductUUID := common.HostSys("class/dmi/id/product_uuid")
|
||||
machineID := common.HostEtc("machine-id")
|
||||
procSysKernelRandomBootID := common.HostProc("sys/kernel/random/boot_id")
|
||||
switch {
|
||||
// In order to read this file, needs to be supported by kernel/arch and run as root
|
||||
// so having fallback is important
|
||||
@@ -95,80 +101,22 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
fallthrough
|
||||
// Not stable between reboot, but better than nothing
|
||||
default:
|
||||
values, err := common.DoSysctrl("kernel.random.boot_id")
|
||||
if err == nil && len(values) == 1 && values[0] != "" {
|
||||
ret.HostID = strings.ToLower(values[0])
|
||||
lines, err := common.ReadLines(procSysKernelRandomBootID)
|
||||
if err == nil && len(lines) > 0 && lines[0] != "" {
|
||||
ret.HostID = strings.ToLower(lines[0])
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// cachedBootTime must be accessed via atomic.Load/StoreUint64
|
||||
var cachedBootTime uint64
|
||||
|
||||
// BootTime returns the system boot time expressed in seconds since the epoch.
|
||||
func BootTime() (uint64, error) {
|
||||
return BootTimeWithContext(context.Background())
|
||||
}
|
||||
|
||||
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
||||
t := atomic.LoadUint64(&cachedBootTime)
|
||||
if t != 0 {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
system, role, err := Virtualization()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
statFile := "stat"
|
||||
if system == "lxc" && role == "guest" {
|
||||
// if lxc, /proc/uptime is used.
|
||||
statFile = "uptime"
|
||||
} else if system == "docker" && role == "guest" {
|
||||
// also docker, guest
|
||||
statFile = "uptime"
|
||||
}
|
||||
|
||||
filename := common.HostProc(statFile)
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if statFile == "stat" {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "btime") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) != 2 {
|
||||
return 0, fmt.Errorf("wrong btime format")
|
||||
}
|
||||
b, err := strconv.ParseInt(f[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
t = uint64(b)
|
||||
atomic.StoreUint64(&cachedBootTime, t)
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
} else if statFile == "uptime" {
|
||||
if len(lines) != 1 {
|
||||
return 0, fmt.Errorf("wrong uptime format")
|
||||
}
|
||||
f := strings.Fields(lines[0])
|
||||
b, err := strconv.ParseFloat(f[0], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
t = uint64(time.Now().Unix()) - uint64(b)
|
||||
atomic.StoreUint64(&cachedBootTime, t)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("could not find btime")
|
||||
return common.BootTimeWithContext(ctx)
|
||||
}
|
||||
|
||||
func uptime(boot uint64) uint64 {
|
||||
@@ -234,26 +182,6 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
|
||||
|
||||
}
|
||||
|
||||
func getOSRelease() (platform string, version string, err error) {
|
||||
contents, err := common.ReadLines(common.HostEtc("os-release"))
|
||||
if err != nil {
|
||||
return "", "", nil // return empty
|
||||
}
|
||||
for _, line := range contents {
|
||||
field := strings.Split(line, "=")
|
||||
if len(field) < 2 {
|
||||
continue
|
||||
}
|
||||
switch field[0] {
|
||||
case "ID": // use ID for lowercase
|
||||
platform = field[1]
|
||||
case "VERSION":
|
||||
version = field[1]
|
||||
}
|
||||
}
|
||||
return platform, version, nil
|
||||
}
|
||||
|
||||
func getLSB() (*LSB, error) {
|
||||
ret := &LSB{}
|
||||
if common.PathExists(common.HostEtc("lsb-release")) {
|
||||
@@ -278,7 +206,7 @@ func getLSB() (*LSB, error) {
|
||||
}
|
||||
}
|
||||
} else if common.PathExists("/usr/bin/lsb_release") {
|
||||
lsb_release, err := exec.LookPath("/usr/bin/lsb_release")
|
||||
lsb_release, err := exec.LookPath("lsb_release")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
@@ -391,7 +319,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
|
||||
version = contents[0]
|
||||
}
|
||||
} else if common.PathExists(common.HostEtc("os-release")) {
|
||||
p, v, err := getOSRelease()
|
||||
p, v, err := common.GetOSRelease()
|
||||
if err == nil {
|
||||
platform = p
|
||||
version = v
|
||||
@@ -420,7 +348,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
|
||||
family = "fedora"
|
||||
case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm":
|
||||
family = "rhel"
|
||||
case "suse", "opensuse":
|
||||
case "suse", "opensuse", "sles":
|
||||
family = "suse"
|
||||
case "gentoo":
|
||||
family = "gentoo"
|
||||
@@ -434,6 +362,8 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
|
||||
family = "alpine"
|
||||
case "coreos":
|
||||
family = "coreos"
|
||||
case "solus":
|
||||
family = "solus"
|
||||
}
|
||||
|
||||
return platform, family, version, nil
|
||||
@@ -445,19 +375,12 @@ func KernelVersion() (version string, err error) {
|
||||
}
|
||||
|
||||
func KernelVersionWithContext(ctx context.Context) (version string, err error) {
|
||||
filename := common.HostProc("sys/kernel/osrelease")
|
||||
if common.PathExists(filename) {
|
||||
contents, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(contents) > 0 {
|
||||
version = contents[0]
|
||||
}
|
||||
var utsname unix.Utsname
|
||||
err = unix.Uname(&utsname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return version, nil
|
||||
return string(utsname.Release[:bytes.IndexByte(utsname.Release[:], 0)]), nil
|
||||
}
|
||||
|
||||
func getSlackwareVersion(contents []string) string {
|
||||
@@ -514,106 +437,7 @@ func Virtualization() (string, string, error) {
|
||||
}
|
||||
|
||||
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
|
||||
var system string
|
||||
var role string
|
||||
|
||||
filename := common.HostProc("xen")
|
||||
if common.PathExists(filename) {
|
||||
system = "xen"
|
||||
role = "guest" // assume guest
|
||||
|
||||
if common.PathExists(filepath.Join(filename, "capabilities")) {
|
||||
contents, err := common.ReadLines(filepath.Join(filename, "capabilities"))
|
||||
if err == nil {
|
||||
if common.StringsContains(contents, "control_d") {
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = common.HostProc("modules")
|
||||
if common.PathExists(filename) {
|
||||
contents, err := common.ReadLines(filename)
|
||||
if err == nil {
|
||||
if common.StringsContains(contents, "kvm") {
|
||||
system = "kvm"
|
||||
role = "host"
|
||||
} else if common.StringsContains(contents, "vboxdrv") {
|
||||
system = "vbox"
|
||||
role = "host"
|
||||
} else if common.StringsContains(contents, "vboxguest") {
|
||||
system = "vbox"
|
||||
role = "guest"
|
||||
} else if common.StringsContains(contents, "vmware") {
|
||||
system = "vmware"
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = common.HostProc("cpuinfo")
|
||||
if common.PathExists(filename) {
|
||||
contents, err := common.ReadLines(filename)
|
||||
if err == nil {
|
||||
if common.StringsContains(contents, "QEMU Virtual CPU") ||
|
||||
common.StringsContains(contents, "Common KVM processor") ||
|
||||
common.StringsContains(contents, "Common 32-bit KVM processor") {
|
||||
system = "kvm"
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = common.HostProc()
|
||||
if common.PathExists(filepath.Join(filename, "bc", "0")) {
|
||||
system = "openvz"
|
||||
role = "host"
|
||||
} else if common.PathExists(filepath.Join(filename, "vz")) {
|
||||
system = "openvz"
|
||||
role = "guest"
|
||||
}
|
||||
|
||||
// not use dmidecode because it requires root
|
||||
if common.PathExists(filepath.Join(filename, "self", "status")) {
|
||||
contents, err := common.ReadLines(filepath.Join(filename, "self", "status"))
|
||||
if err == nil {
|
||||
|
||||
if common.StringsContains(contents, "s_context:") ||
|
||||
common.StringsContains(contents, "VxID:") {
|
||||
system = "linux-vserver"
|
||||
}
|
||||
// TODO: guest or host
|
||||
}
|
||||
}
|
||||
|
||||
if common.PathExists(filepath.Join(filename, "self", "cgroup")) {
|
||||
contents, err := common.ReadLines(filepath.Join(filename, "self", "cgroup"))
|
||||
if err == nil {
|
||||
if common.StringsContains(contents, "lxc") {
|
||||
system = "lxc"
|
||||
role = "guest"
|
||||
} else if common.StringsContains(contents, "docker") {
|
||||
system = "docker"
|
||||
role = "guest"
|
||||
} else if common.StringsContains(contents, "machine-rkt") {
|
||||
system = "rkt"
|
||||
role = "guest"
|
||||
} else if common.PathExists("/usr/bin/lxc-version") {
|
||||
system = "lxc"
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if common.PathExists(common.HostEtc("os-release")) {
|
||||
p, _, err := getOSRelease()
|
||||
if err == nil && p == "coreos" {
|
||||
system = "rkt" // Is it true?
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
return system, role, nil
|
||||
return common.VirtualizationWithContext(ctx)
|
||||
}
|
||||
|
||||
func SensorsTemperatures() ([]TemperatureStat, error) {
|
||||
@@ -634,6 +458,7 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err
|
||||
return temperatures, err
|
||||
}
|
||||
}
|
||||
var warns Warnings
|
||||
|
||||
// example directory
|
||||
// device/ temp1_crit_alarm temp2_crit_alarm temp3_crit_alarm temp4_crit_alarm temp5_crit_alarm temp6_crit_alarm temp7_crit_alarm
|
||||
@@ -656,19 +481,22 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err
|
||||
label = fmt.Sprintf("%s_", strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(c))), " "), ""))
|
||||
}
|
||||
|
||||
// Get the name of the tempearture you are reading
|
||||
// Get the name of the temperature you are reading
|
||||
name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(file), "name"))
|
||||
if err != nil {
|
||||
return temperatures, err
|
||||
warns.Add(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the temperature reading
|
||||
current, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return temperatures, err
|
||||
warns.Add(err)
|
||||
continue
|
||||
}
|
||||
temperature, err := strconv.ParseFloat(strings.TrimSpace(string(current)), 64)
|
||||
if err != nil {
|
||||
warns.Add(err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -678,5 +506,5 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err
|
||||
Temperature: temperature / 1000.0,
|
||||
})
|
||||
}
|
||||
return temperatures, nil
|
||||
return temperatures, warns.Reference()
|
||||
}
|
||||
|
||||
+24
-16
@@ -8,15 +8,15 @@ import (
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,6 +40,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
ret.Hostname = hostname
|
||||
}
|
||||
|
||||
kernelArch, err := kernelArch()
|
||||
if err == nil {
|
||||
ret.KernelArch = kernelArch
|
||||
}
|
||||
|
||||
platform, family, version, err := PlatformInformation()
|
||||
if err == nil {
|
||||
ret.Platform = platform
|
||||
@@ -66,20 +71,28 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// cachedBootTime must be accessed via atomic.Load/StoreUint64
|
||||
var cachedBootTime uint64
|
||||
|
||||
func BootTime() (uint64, error) {
|
||||
return BootTimeWithContext(context.Background())
|
||||
}
|
||||
|
||||
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
||||
val, err := common.DoSysctrl("kern.boottime")
|
||||
// https://github.com/AaronO/dashd/blob/222e32ef9f7a1f9bea4a8da2c3627c4cb992f860/probe/probe_darwin.go
|
||||
t := atomic.LoadUint64(&cachedBootTime)
|
||||
if t != 0 {
|
||||
return t, nil
|
||||
}
|
||||
value, err := unix.Sysctl("kern.boottime")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
bytes := []byte(value[:])
|
||||
var boottime uint64
|
||||
boottime = uint64(bytes[0]) + uint64(bytes[1])*256 + uint64(bytes[2])*256*256 + uint64(bytes[3])*256*256*256
|
||||
|
||||
boottime, err := strconv.ParseUint(val[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
atomic.StoreUint64(&cachedBootTime, boottime)
|
||||
|
||||
return boottime, nil
|
||||
}
|
||||
@@ -108,19 +121,14 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string
|
||||
platform := ""
|
||||
family := ""
|
||||
version := ""
|
||||
uname, err := exec.LookPath("uname")
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
out, err := invoke.CommandWithContext(ctx, uname, "-s")
|
||||
p, err := unix.Sysctl("kern.ostype")
|
||||
if err == nil {
|
||||
platform = strings.ToLower(strings.TrimSpace(string(out)))
|
||||
platform = strings.ToLower(p)
|
||||
}
|
||||
|
||||
out, err = invoke.CommandWithContext(ctx, uname, "-r")
|
||||
v, err := unix.Sysctl("kern.osrelease")
|
||||
if err == nil {
|
||||
version = strings.ToLower(strings.TrimSpace(string(out)))
|
||||
version = strings.ToLower(v)
|
||||
}
|
||||
|
||||
return platform, family, version, nil
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// +build linux freebsd openbsd darwin solaris
|
||||
|
||||
package host
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func kernelArch() (string, error) {
|
||||
var utsname unix.Utsname
|
||||
err := unix.Uname(&utsname)
|
||||
return string(utsname.Machine[:bytes.IndexByte(utsname.Machine[:], 0)]), err
|
||||
}
|
||||
+10
-5
@@ -33,7 +33,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
result.Hostname = hostname
|
||||
|
||||
// Parse versions from output of `uname(1)`
|
||||
uname, err := exec.LookPath("/usr/bin/uname")
|
||||
uname, err := exec.LookPath("uname")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -54,6 +54,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
result.PlatformVersion = fields[2]
|
||||
}
|
||||
|
||||
kernelArch, err := kernelArch()
|
||||
if err == nil {
|
||||
result.KernelArch = kernelArch
|
||||
}
|
||||
|
||||
// Find distribution name from /etc/release
|
||||
fh, err := os.Open("/etc/release")
|
||||
if err != nil {
|
||||
@@ -85,7 +90,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
switch result.Platform {
|
||||
case "SmartOS":
|
||||
// If everything works, use the current zone ID as the HostID if present.
|
||||
zonename, err := exec.LookPath("/usr/bin/zonename")
|
||||
zonename, err := exec.LookPath("zonename")
|
||||
if err == nil {
|
||||
out, err := invoke.CommandWithContext(ctx, zonename)
|
||||
if err == nil {
|
||||
@@ -112,7 +117,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
// this point there are no hardware facilities available. This behavior
|
||||
// matches that of other supported OSes.
|
||||
if result.HostID == "" {
|
||||
hostID, err := exec.LookPath("/usr/bin/hostid")
|
||||
hostID, err := exec.LookPath("hostid")
|
||||
if err == nil {
|
||||
out, err := invoke.CommandWithContext(ctx, hostID)
|
||||
if err == nil {
|
||||
@@ -151,7 +156,7 @@ func BootTime() (uint64, error) {
|
||||
}
|
||||
|
||||
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
||||
kstat, err := exec.LookPath("/usr/bin/kstat")
|
||||
kstat, err := exec.LookPath("kstat")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -215,7 +220,7 @@ func KernelVersion() (string, error) {
|
||||
|
||||
func KernelVersionWithContext(ctx context.Context) (string, error) {
|
||||
// Parse versions from output of `uname(1)`
|
||||
uname, err := exec.LookPath("/usr/bin/uname")
|
||||
uname, err := exec.LookPath("uname")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
+58
-4
@@ -24,6 +24,7 @@ var (
|
||||
procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime")
|
||||
procGetTickCount32 = common.Modkernel32.NewProc("GetTickCount")
|
||||
procGetTickCount64 = common.Modkernel32.NewProc("GetTickCount64")
|
||||
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
|
||||
procRtlGetVersion = common.ModNt.NewProc("RtlGetVersion")
|
||||
)
|
||||
|
||||
@@ -42,6 +43,20 @@ type osVersionInfoExW struct {
|
||||
wReserved uint8
|
||||
}
|
||||
|
||||
type systemInfo struct {
|
||||
wProcessorArchitecture uint16
|
||||
wReserved uint16
|
||||
dwPageSize uint32
|
||||
lpMinimumApplicationAddress uintptr
|
||||
lpMaximumApplicationAddress uintptr
|
||||
dwActiveProcessorMask uintptr
|
||||
dwNumberOfProcessors uint32
|
||||
dwProcessorType uint32
|
||||
dwAllocationGranularity uint32
|
||||
wProcessorLevel uint16
|
||||
wProcessorRevision uint16
|
||||
}
|
||||
|
||||
type msAcpi_ThermalZoneTemperature struct {
|
||||
Active bool
|
||||
CriticalTripPoint uint32
|
||||
@@ -77,7 +92,14 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
}
|
||||
|
||||
{
|
||||
boot, err := BootTime()
|
||||
kernelArch, err := kernelArch()
|
||||
if err == nil {
|
||||
ret.KernelArch = kernelArch
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
boot, err := BootTimeWithContext(ctx)
|
||||
if err == nil {
|
||||
ret.BootTime = boot
|
||||
ret.Uptime, _ = Uptime()
|
||||
@@ -87,12 +109,12 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
|
||||
{
|
||||
hostID, err := getMachineGuid()
|
||||
if err == nil {
|
||||
ret.HostID = strings.ToLower(hostID)
|
||||
ret.HostID = hostID
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
procs, err := process.Pids()
|
||||
procs, err := process.PidsWithContext(ctx)
|
||||
if err == nil {
|
||||
ret.Procs = uint64(len(procs))
|
||||
}
|
||||
@@ -128,7 +150,7 @@ func getMachineGuid() (string, error) {
|
||||
return "", fmt.Errorf("HostID incorrect: %q\n", hostID)
|
||||
}
|
||||
|
||||
return hostID, nil
|
||||
return strings.ToLower(hostID), nil
|
||||
}
|
||||
|
||||
func Uptime() (uint64, error) {
|
||||
@@ -293,3 +315,35 @@ func KernelVersionWithContext(ctx context.Context) (string, error) {
|
||||
_, _, version, err := PlatformInformation()
|
||||
return version, err
|
||||
}
|
||||
|
||||
func kernelArch() (string, error) {
|
||||
var systemInfo systemInfo
|
||||
procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
|
||||
|
||||
const (
|
||||
PROCESSOR_ARCHITECTURE_INTEL = 0
|
||||
PROCESSOR_ARCHITECTURE_ARM = 5
|
||||
PROCESSOR_ARCHITECTURE_ARM64 = 12
|
||||
PROCESSOR_ARCHITECTURE_IA64 = 6
|
||||
PROCESSOR_ARCHITECTURE_AMD64 = 9
|
||||
)
|
||||
switch systemInfo.wProcessorArchitecture {
|
||||
case PROCESSOR_ARCHITECTURE_INTEL:
|
||||
if systemInfo.wProcessorLevel < 3 {
|
||||
return "i386", nil
|
||||
}
|
||||
if systemInfo.wProcessorLevel > 6 {
|
||||
return "i686", nil
|
||||
}
|
||||
return fmt.Sprintf("i%d86", systemInfo.wProcessorLevel), nil
|
||||
case PROCESSOR_ARCHITECTURE_ARM:
|
||||
return "arm", nil
|
||||
case PROCESSOR_ARCHITECTURE_ARM64:
|
||||
return "aarch64", nil
|
||||
case PROCESSOR_ARCHITECTURE_IA64:
|
||||
return "ia64", nil
|
||||
case PROCESSOR_ARCHITECTURE_AMD64:
|
||||
return "x86_64", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Warnings struct {
|
||||
List []error
|
||||
}
|
||||
|
||||
func (w *Warnings) Add(err error) {
|
||||
w.List = append(w.List, err)
|
||||
}
|
||||
|
||||
func (w *Warnings) Reference() error {
|
||||
if len(w.List) > 0 {
|
||||
return w
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Warnings) Error() string {
|
||||
return fmt.Sprintf("Number of warnings: %v", len(w.List))
|
||||
}
|
||||
+4
-4
@@ -11,6 +11,7 @@ package host
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <utmpx.h>
|
||||
#include "freebsd_headers/utxdb.h"
|
||||
|
||||
enum {
|
||||
sizeofPtr = sizeof(void*),
|
||||
@@ -27,7 +28,7 @@ const (
|
||||
sizeofInt = C.sizeof_int
|
||||
sizeofLong = C.sizeof_long
|
||||
sizeofLongLong = C.sizeof_longlong
|
||||
sizeOfUtmpx = C.sizeof_struct_utmpx
|
||||
sizeOfUtmpx = C.sizeof_struct_futx
|
||||
)
|
||||
|
||||
// Basic types
|
||||
@@ -39,6 +40,5 @@ type (
|
||||
_C_long_long C.longlong
|
||||
)
|
||||
|
||||
type Utmp C.struct_utmp
|
||||
type Utmpx C.struct_utmpx
|
||||
type Timeval C.struct_timeval
|
||||
type Utmp C.struct_utmp // for FreeBSD 9.0 compatibility
|
||||
type Utmpx C.struct_futx
|
||||
|
||||
+6
-43
@@ -213,6 +213,12 @@ func ReadInts(filename string) ([]int64, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Parse Hex to uint32 without error
|
||||
func HexToUint32(hex string) uint32 {
|
||||
vv, _ := strconv.ParseUint(hex, 16, 32)
|
||||
return uint32(vv)
|
||||
}
|
||||
|
||||
// Parse to int32 without error
|
||||
func mustParseInt32(val string) int32 {
|
||||
vv, _ := strconv.ParseInt(val, 10, 32)
|
||||
@@ -332,49 +338,6 @@ func HostRun(combineWith ...string) string {
|
||||
return GetEnv("HOST_RUN", "/run", combineWith...)
|
||||
}
|
||||
|
||||
// https://gist.github.com/kylelemons/1525278
|
||||
func Pipeline(cmds ...*exec.Cmd) ([]byte, []byte, error) {
|
||||
// Require at least one command
|
||||
if len(cmds) < 1 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Collect the output from the command(s)
|
||||
var output bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
last := len(cmds) - 1
|
||||
for i, cmd := range cmds[:last] {
|
||||
var err error
|
||||
// Connect each command's stdin to the previous command's stdout
|
||||
if cmds[i+1].Stdin, err = cmd.StdoutPipe(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Connect each command's stderr to a buffer
|
||||
cmd.Stderr = &stderr
|
||||
}
|
||||
|
||||
// Connect the output and error for the last command
|
||||
cmds[last].Stdout, cmds[last].Stderr = &output, &stderr
|
||||
|
||||
// Start each command
|
||||
for _, cmd := range cmds {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return output.Bytes(), stderr.Bytes(), err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for each command to complete
|
||||
for _, cmd := range cmds {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return output.Bytes(), stderr.Bytes(), err
|
||||
}
|
||||
}
|
||||
|
||||
// Return the pipeline output and the collected standard error
|
||||
return output.Bytes(), stderr.Bytes(), nil
|
||||
}
|
||||
|
||||
// getSysctrlEnv sets LC_ALL=C in a list of env vars for use when running
|
||||
// sysctl commands (see DoSysctrl).
|
||||
func getSysctrlEnv(env []string) []string {
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func DoSysctrlWithContext(ctx context.Context, mib string) ([]string, error) {
|
||||
sysctl, err := exec.LookPath("/usr/sbin/sysctl")
|
||||
sysctl, err := exec.LookPath("sysctl")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
+17
-1
@@ -3,6 +3,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -11,8 +12,23 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func SysctlUint(mib string) (uint64, error) {
|
||||
buf, err := unix.SysctlRaw(mib)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(buf) == 8 { // 64 bit
|
||||
return *(*uint64)(unsafe.Pointer(&buf[0])), nil
|
||||
}
|
||||
if len(buf) == 4 { // 32bit
|
||||
t := *(*uint32)(unsafe.Pointer(&buf[0]))
|
||||
return uint64(t), nil
|
||||
}
|
||||
return 0, fmt.Errorf("unexpected size: %s, %d", mib, len(buf))
|
||||
}
|
||||
|
||||
func DoSysctrl(mib string) ([]string, error) {
|
||||
sysctl, err := exec.LookPath("/sbin/sysctl")
|
||||
sysctl, err := exec.LookPath("sysctl")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
+225
-2
@@ -3,13 +3,19 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DoSysctrl(mib string) ([]string, error) {
|
||||
sysctl, err := exec.LookPath("/sbin/sysctl")
|
||||
sysctl, err := exec.LookPath("sysctl")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
@@ -37,5 +43,222 @@ func NumProcs() (uint64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(len(list)), err
|
||||
var cnt uint64
|
||||
|
||||
for _, v := range list {
|
||||
if _, err = strconv.ParseUint(v, 10, 64); err == nil {
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
// cachedBootTime must be accessed via atomic.Load/StoreUint64
|
||||
var cachedBootTime uint64
|
||||
|
||||
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
||||
t := atomic.LoadUint64(&cachedBootTime)
|
||||
if t != 0 {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
system, role, err := Virtualization()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
statFile := "stat"
|
||||
if system == "lxc" && role == "guest" {
|
||||
// if lxc, /proc/uptime is used.
|
||||
statFile = "uptime"
|
||||
} else if system == "docker" && role == "guest" {
|
||||
// also docker, guest
|
||||
statFile = "uptime"
|
||||
}
|
||||
|
||||
filename := HostProc(statFile)
|
||||
lines, err := ReadLines(filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if statFile == "stat" {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "btime") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) != 2 {
|
||||
return 0, fmt.Errorf("wrong btime format")
|
||||
}
|
||||
b, err := strconv.ParseInt(f[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
t = uint64(b)
|
||||
atomic.StoreUint64(&cachedBootTime, t)
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
} else if statFile == "uptime" {
|
||||
if len(lines) != 1 {
|
||||
return 0, fmt.Errorf("wrong uptime format")
|
||||
}
|
||||
f := strings.Fields(lines[0])
|
||||
b, err := strconv.ParseFloat(f[0], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
t = uint64(time.Now().Unix()) - uint64(b)
|
||||
atomic.StoreUint64(&cachedBootTime, t)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("could not find btime")
|
||||
}
|
||||
|
||||
func Virtualization() (string, string, error) {
|
||||
return VirtualizationWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
|
||||
var system string
|
||||
var role string
|
||||
|
||||
filename := HostProc("xen")
|
||||
if PathExists(filename) {
|
||||
system = "xen"
|
||||
role = "guest" // assume guest
|
||||
|
||||
if PathExists(filepath.Join(filename, "capabilities")) {
|
||||
contents, err := ReadLines(filepath.Join(filename, "capabilities"))
|
||||
if err == nil {
|
||||
if StringsContains(contents, "control_d") {
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProc("modules")
|
||||
if PathExists(filename) {
|
||||
contents, err := ReadLines(filename)
|
||||
if err == nil {
|
||||
if StringsContains(contents, "kvm") {
|
||||
system = "kvm"
|
||||
role = "host"
|
||||
} else if StringsContains(contents, "vboxdrv") {
|
||||
system = "vbox"
|
||||
role = "host"
|
||||
} else if StringsContains(contents, "vboxguest") {
|
||||
system = "vbox"
|
||||
role = "guest"
|
||||
} else if StringsContains(contents, "vmware") {
|
||||
system = "vmware"
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProc("cpuinfo")
|
||||
if PathExists(filename) {
|
||||
contents, err := ReadLines(filename)
|
||||
if err == nil {
|
||||
if StringsContains(contents, "QEMU Virtual CPU") ||
|
||||
StringsContains(contents, "Common KVM processor") ||
|
||||
StringsContains(contents, "Common 32-bit KVM processor") {
|
||||
system = "kvm"
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProc("bus/pci/devices")
|
||||
if PathExists(filename) {
|
||||
contents, err := ReadLines(filename)
|
||||
if err == nil {
|
||||
if StringsContains(contents, "virtio-pci") {
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProc()
|
||||
if PathExists(filepath.Join(filename, "bc", "0")) {
|
||||
system = "openvz"
|
||||
role = "host"
|
||||
} else if PathExists(filepath.Join(filename, "vz")) {
|
||||
system = "openvz"
|
||||
role = "guest"
|
||||
}
|
||||
|
||||
// not use dmidecode because it requires root
|
||||
if PathExists(filepath.Join(filename, "self", "status")) {
|
||||
contents, err := ReadLines(filepath.Join(filename, "self", "status"))
|
||||
if err == nil {
|
||||
|
||||
if StringsContains(contents, "s_context:") ||
|
||||
StringsContains(contents, "VxID:") {
|
||||
system = "linux-vserver"
|
||||
}
|
||||
// TODO: guest or host
|
||||
}
|
||||
}
|
||||
|
||||
if PathExists(filepath.Join(filename, "self", "cgroup")) {
|
||||
contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
|
||||
if err == nil {
|
||||
if StringsContains(contents, "lxc") {
|
||||
system = "lxc"
|
||||
role = "guest"
|
||||
} else if StringsContains(contents, "docker") {
|
||||
system = "docker"
|
||||
role = "guest"
|
||||
} else if StringsContains(contents, "machine-rkt") {
|
||||
system = "rkt"
|
||||
role = "guest"
|
||||
} else if PathExists("/usr/bin/lxc-version") {
|
||||
system = "lxc"
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if PathExists(HostEtc("os-release")) {
|
||||
p, _, err := GetOSRelease()
|
||||
if err == nil && p == "coreos" {
|
||||
system = "rkt" // Is it true?
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
return system, role, nil
|
||||
}
|
||||
|
||||
func GetOSRelease() (platform string, version string, err error) {
|
||||
contents, err := ReadLines(HostEtc("os-release"))
|
||||
if err != nil {
|
||||
return "", "", nil // return empty
|
||||
}
|
||||
for _, line := range contents {
|
||||
field := strings.Split(line, "=")
|
||||
if len(field) < 2 {
|
||||
continue
|
||||
}
|
||||
switch field[0] {
|
||||
case "ID": // use ID for lowercase
|
||||
platform = trimQuotes(field[1])
|
||||
case "VERSION":
|
||||
version = trimQuotes(field[1])
|
||||
}
|
||||
}
|
||||
return platform, version, nil
|
||||
}
|
||||
|
||||
// Remove quotes of the source string
|
||||
func trimQuotes(s string) string {
|
||||
if len(s) >= 2 {
|
||||
if s[0] == '"' && s[len(s)-1] == '"' {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func DoSysctrl(mib string) ([]string, error) {
|
||||
sysctl, err := exec.LookPath("/sbin/sysctl")
|
||||
sysctl, err := exec.LookPath("sysctl")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args ..
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, lsof, cmd...)
|
||||
if err != nil {
|
||||
// if no pid found, lsof returnes code 1.
|
||||
// if no pid found, lsof returns code 1.
|
||||
if err.Error() == "exit status 1" && len(out) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
+25
@@ -4,6 +4,9 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/StackExchange/wmi"
|
||||
@@ -59,6 +62,8 @@ var (
|
||||
PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData")
|
||||
PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue")
|
||||
PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery")
|
||||
|
||||
procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW")
|
||||
)
|
||||
|
||||
type FILETIME struct {
|
||||
@@ -133,3 +138,23 @@ func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, con
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Convert paths using native DOS format like:
|
||||
// "\Device\HarddiskVolume1\Windows\systemew\file.txt"
|
||||
// into:
|
||||
// "C:\Windows\systemew\file.txt"
|
||||
func ConvertDOSPath(p string) string {
|
||||
rawDrive := strings.Join(strings.Split(p, `\`)[:3], `\`)
|
||||
|
||||
for d := 'A'; d <= 'Z'; d++ {
|
||||
szDeviceName := string(d) + ":"
|
||||
szTarget := make([]uint16, 512)
|
||||
ret, _, _ := procQueryDosDeviceW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(szDeviceName))),
|
||||
uintptr(unsafe.Pointer(&szTarget[0])),
|
||||
uintptr(len(szTarget)))
|
||||
if ret != 0 && windows.UTF16ToString(szTarget[:]) == rawDrive {
|
||||
return filepath.Join(szDeviceName, p[len(rawDrive):])
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package load
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
|
||||
var invoke common.Invoker = common.Invoke{}
|
||||
|
||||
type AvgStat struct {
|
||||
Load1 float64 `json:"load1"`
|
||||
Load5 float64 `json:"load5"`
|
||||
Load15 float64 `json:"load15"`
|
||||
}
|
||||
|
||||
func (l AvgStat) String() string {
|
||||
s, _ := json.Marshal(l)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
type MiscStat struct {
|
||||
ProcsTotal int `json:"procsTotal"`
|
||||
ProcsRunning int `json:"procsRunning"`
|
||||
ProcsBlocked int `json:"procsBlocked"`
|
||||
Ctxt int `json:"ctxt"`
|
||||
}
|
||||
|
||||
func (m MiscStat) String() string {
|
||||
s, _ := json.Marshal(m)
|
||||
return string(s)
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// +build freebsd openbsd
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func Avg() (*AvgStat, error) {
|
||||
return AvgWithContext(context.Background())
|
||||
}
|
||||
|
||||
func AvgWithContext(ctx context.Context) (*AvgStat, error) {
|
||||
// This SysctlRaw method borrowed from
|
||||
// https://github.com/prometheus/node_exporter/blob/master/collector/loadavg_freebsd.go
|
||||
type loadavg struct {
|
||||
load [3]uint32
|
||||
scale int
|
||||
}
|
||||
b, err := unix.SysctlRaw("vm.loadavg")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
load := *(*loadavg)(unsafe.Pointer((&b[0])))
|
||||
scale := float64(load.scale)
|
||||
ret := &AvgStat{
|
||||
Load1: float64(load.load[0]) / scale,
|
||||
Load5: float64(load.load[1]) / scale,
|
||||
Load15: float64(load.load[2]) / scale,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Misc returns miscellaneous host-wide statistics.
|
||||
// darwin use ps command to get process running/blocked count.
|
||||
// Almost same as Darwin implementation, but state is different.
|
||||
func Misc() (*MiscStat, error) {
|
||||
return MiscWithContext(context.Background())
|
||||
}
|
||||
|
||||
func MiscWithContext(ctx context.Context) (*MiscStat, error) {
|
||||
bin, err := exec.LookPath("ps")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, bin, "axo", "state")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
|
||||
ret := MiscStat{}
|
||||
for _, l := range lines {
|
||||
if strings.Contains(l, "R") {
|
||||
ret.ProcsRunning++
|
||||
} else if strings.Contains(l, "D") {
|
||||
ret.ProcsBlocked++
|
||||
}
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// +build darwin
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func Avg() (*AvgStat, error) {
|
||||
return AvgWithContext(context.Background())
|
||||
}
|
||||
|
||||
func AvgWithContext(ctx context.Context) (*AvgStat, error) {
|
||||
// This SysctlRaw method borrowed from
|
||||
// https://github.com/prometheus/node_exporter/blob/master/collector/loadavg_freebsd.go
|
||||
// this implementation is common with BSDs
|
||||
type loadavg struct {
|
||||
load [3]uint32
|
||||
scale int
|
||||
}
|
||||
b, err := unix.SysctlRaw("vm.loadavg")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
load := *(*loadavg)(unsafe.Pointer((&b[0])))
|
||||
scale := float64(load.scale)
|
||||
ret := &AvgStat{
|
||||
Load1: float64(load.load[0]) / scale,
|
||||
Load5: float64(load.load[1]) / scale,
|
||||
Load15: float64(load.load[2]) / scale,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Misc returnes miscellaneous host-wide statistics.
|
||||
// darwin use ps command to get process running/blocked count.
|
||||
// Almost same as FreeBSD implementation, but state is different.
|
||||
// U means 'Uninterruptible Sleep'.
|
||||
func Misc() (*MiscStat, error) {
|
||||
return MiscWithContext(context.Background())
|
||||
}
|
||||
|
||||
func MiscWithContext(ctx context.Context) (*MiscStat, error) {
|
||||
bin, err := exec.LookPath("ps")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, bin, "axo", "state")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
|
||||
ret := MiscStat{}
|
||||
for _, l := range lines {
|
||||
if strings.Contains(l, "R") {
|
||||
ret.ProcsRunning++
|
||||
} else if strings.Contains(l, "U") {
|
||||
// uninterruptible sleep == blocked
|
||||
ret.ProcsBlocked++
|
||||
}
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// +build !darwin,!linux,!freebsd,!openbsd,!windows
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
|
||||
func Avg() (*AvgStat, error) {
|
||||
return AvgWithContext(context.Background())
|
||||
}
|
||||
|
||||
func AvgWithContext(ctx context.Context) (*AvgStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func Misc() (*MiscStat, error) {
|
||||
return MiscWithContext(context.Background())
|
||||
}
|
||||
|
||||
func MiscWithContext(ctx context.Context) (*MiscStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// +build linux
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
|
||||
func Avg() (*AvgStat, error) {
|
||||
return AvgWithContext(context.Background())
|
||||
}
|
||||
|
||||
func AvgWithContext(ctx context.Context) (*AvgStat, error) {
|
||||
values, err := readLoadAvgFromFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
load1, err := strconv.ParseFloat(values[0], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
load5, err := strconv.ParseFloat(values[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
load15, err := strconv.ParseFloat(values[2], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &AvgStat{
|
||||
Load1: load1,
|
||||
Load5: load5,
|
||||
Load15: load15,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Misc returnes miscellaneous host-wide statistics.
|
||||
// Note: the name should be changed near future.
|
||||
func Misc() (*MiscStat, error) {
|
||||
return MiscWithContext(context.Background())
|
||||
}
|
||||
|
||||
func MiscWithContext(ctx context.Context) (*MiscStat, error) {
|
||||
filename := common.HostProc("stat")
|
||||
out, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &MiscStat{}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
v, err := strconv.ParseInt(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
switch fields[0] {
|
||||
case "procs_running":
|
||||
ret.ProcsRunning = int(v)
|
||||
case "procs_blocked":
|
||||
ret.ProcsBlocked = int(v)
|
||||
case "ctxt":
|
||||
ret.Ctxt = int(v)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
procsTotal, err := getProcsTotal()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret.ProcsTotal = int(procsTotal)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getProcsTotal() (int64, error) {
|
||||
values, err := readLoadAvgFromFile()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseInt(strings.Split(values[3], "/")[1], 10, 64)
|
||||
}
|
||||
|
||||
func readLoadAvgFromFile() ([]string, error) {
|
||||
loadavgFilename := common.HostProc("loadavg")
|
||||
line, err := ioutil.ReadFile(loadavgFilename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := strings.Fields(string(line))
|
||||
return values, nil
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// +build windows
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
|
||||
func Avg() (*AvgStat, error) {
|
||||
return AvgWithContext(context.Background())
|
||||
}
|
||||
|
||||
func AvgWithContext(ctx context.Context) (*AvgStat, error) {
|
||||
ret := AvgStat{}
|
||||
|
||||
return &ret, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func Misc() (*MiscStat, error) {
|
||||
return MiscWithContext(context.Background())
|
||||
}
|
||||
|
||||
func MiscWithContext(ctx context.Context) (*MiscStat, error) {
|
||||
ret := MiscStat{}
|
||||
|
||||
return &ret, common.ErrNotImplementedError
|
||||
}
|
||||
+4
@@ -58,6 +58,7 @@ type VirtualMemoryStat struct {
|
||||
Shared uint64 `json:"shared"`
|
||||
Slab uint64 `json:"slab"`
|
||||
SReclaimable uint64 `json:"sreclaimable"`
|
||||
SUnreclaim uint64 `json:"sunreclaim"`
|
||||
PageTables uint64 `json:"pagetables"`
|
||||
SwapCached uint64 `json:"swapcached"`
|
||||
CommitLimit uint64 `json:"commitlimit"`
|
||||
@@ -84,6 +85,9 @@ type SwapMemoryStat struct {
|
||||
UsedPercent float64 `json:"usedPercent"`
|
||||
Sin uint64 `json:"sin"`
|
||||
Sout uint64 `json:"sout"`
|
||||
PgIn uint64 `json:"pgin"`
|
||||
PgOut uint64 `json:"pgout"`
|
||||
PgFault uint64 `json:"pgfault"`
|
||||
}
|
||||
|
||||
func (m VirtualMemoryStat) String() string {
|
||||
|
||||
+21
-26
@@ -5,10 +5,9 @@ package mem
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
"strings"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@@ -27,46 +26,42 @@ func getHwMemsize() (uint64, error) {
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// xsw_usage in sys/sysctl.h
|
||||
type swapUsage struct {
|
||||
Total uint64
|
||||
Avail uint64
|
||||
Used uint64
|
||||
Pagesize int32
|
||||
Encrypted bool
|
||||
}
|
||||
|
||||
// SwapMemory returns swapinfo.
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
// https://github.com/yanllearnn/go-osstat/blob/ae8a279d26f52ec946a03698c7f50a26cfb427e3/memory/memory_darwin.go
|
||||
var ret *SwapMemoryStat
|
||||
|
||||
swapUsage, err := common.DoSysctrlWithContext(ctx, "vm.swapusage")
|
||||
value, err := unix.SysctlRaw("vm.swapusage")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
total := strings.Replace(swapUsage[2], "M", "", 1)
|
||||
used := strings.Replace(swapUsage[5], "M", "", 1)
|
||||
free := strings.Replace(swapUsage[8], "M", "", 1)
|
||||
|
||||
total_v, err := strconv.ParseFloat(total, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
used_v, err := strconv.ParseFloat(used, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
free_v, err := strconv.ParseFloat(free, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(value) != 32 {
|
||||
return ret, fmt.Errorf("unexpected output of sysctl vm.swapusage: %v (len: %d)", value, len(value))
|
||||
}
|
||||
swap := (*swapUsage)(unsafe.Pointer(&value[0]))
|
||||
|
||||
u := float64(0)
|
||||
if total_v != 0 {
|
||||
u = ((total_v - free_v) / total_v) * 100.0
|
||||
if swap.Total != 0 {
|
||||
u = ((float64(swap.Total) - float64(swap.Avail)) / float64(swap.Total)) * 100.0
|
||||
}
|
||||
|
||||
// vm.swapusage shows "M", multiply 1024 * 1024 to convert bytes.
|
||||
ret = &SwapMemoryStat{
|
||||
Total: uint64(total_v * 1024 * 1024),
|
||||
Used: uint64(used_v * 1024 * 1024),
|
||||
Free: uint64(free_v * 1024 * 1024),
|
||||
Total: swap.Total,
|
||||
Used: swap.Used,
|
||||
Free: swap.Avail,
|
||||
UsedPercent: u,
|
||||
}
|
||||
|
||||
|
||||
+52
-27
@@ -8,6 +8,8 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/internal/common"
|
||||
)
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
@@ -15,58 +17,59 @@ func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
pageSize, err := unix.SysctlUint32("vm.stats.vm.v_page_size")
|
||||
pageSize, err := common.SysctlUint("vm.stats.vm.v_page_size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
physmem, err := unix.SysctlUint64("hw.physmem")
|
||||
physmem, err := common.SysctlUint("hw.physmem")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
free, err := unix.SysctlUint32("vm.stats.vm.v_free_count")
|
||||
|
||||
free, err := common.SysctlUint("vm.stats.vm.v_free_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
active, err := unix.SysctlUint32("vm.stats.vm.v_active_count")
|
||||
active, err := common.SysctlUint("vm.stats.vm.v_active_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inactive, err := unix.SysctlUint32("vm.stats.vm.v_inactive_count")
|
||||
inactive, err := common.SysctlUint("vm.stats.vm.v_inactive_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffers, err := unix.SysctlUint64("vfs.bufspace")
|
||||
buffers, err := common.SysctlUint("vfs.bufspace")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wired, err := unix.SysctlUint32("vm.stats.vm.v_wire_count")
|
||||
wired, err := common.SysctlUint("vm.stats.vm.v_wire_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cached, laundry uint32
|
||||
osreldate, _ := unix.SysctlUint32("kern.osreldate")
|
||||
var cached, laundry uint64
|
||||
osreldate, _ := common.SysctlUint("kern.osreldate")
|
||||
if osreldate < 1102000 {
|
||||
cached, err = unix.SysctlUint32("vm.stats.vm.v_cache_count")
|
||||
cached, err = common.SysctlUint("vm.stats.vm.v_cache_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
laundry, err = unix.SysctlUint32("vm.stats.vm.v_laundry_count")
|
||||
laundry, err = common.SysctlUint("vm.stats.vm.v_laundry_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p := uint64(pageSize)
|
||||
p := pageSize
|
||||
ret := &VirtualMemoryStat{
|
||||
Total: uint64(physmem),
|
||||
Free: uint64(free) * p,
|
||||
Active: uint64(active) * p,
|
||||
Inactive: uint64(inactive) * p,
|
||||
Cached: uint64(cached) * p,
|
||||
Buffers: uint64(buffers),
|
||||
Wired: uint64(wired) * p,
|
||||
Laundry: uint64(laundry) * p,
|
||||
Total: physmem,
|
||||
Free: free * p,
|
||||
Active: active * p,
|
||||
Inactive: inactive * p,
|
||||
Cached: cached * p,
|
||||
Buffers: buffers,
|
||||
Wired: wired * p,
|
||||
Laundry: laundry * p,
|
||||
}
|
||||
|
||||
ret.Available = ret.Inactive + ret.Cached + ret.Free + ret.Laundry
|
||||
@@ -84,11 +87,22 @@ func SwapMemory() (*SwapMemoryStat, error) {
|
||||
// Constants from vm/vm_param.h
|
||||
// nolint: golint
|
||||
const (
|
||||
XSWDEV_VERSION = 1
|
||||
XSWDEV_VERSION11 = 1
|
||||
XSWDEV_VERSION = 2
|
||||
)
|
||||
|
||||
// Types from vm/vm_param.h
|
||||
type xswdev struct {
|
||||
Version uint32 // Version is the version
|
||||
Dev uint64 // Dev is the device identifier
|
||||
Flags int32 // Flags is the swap flags applied to the device
|
||||
NBlks int32 // NBlks is the total number of blocks
|
||||
Used int32 // Used is the number of blocks used
|
||||
}
|
||||
|
||||
// xswdev11 is a compatibility for under FreeBSD 11
|
||||
// sys/vm/swap_pager.c
|
||||
type xswdev11 struct {
|
||||
Version uint32 // Version is the version
|
||||
Dev uint32 // Dev is the device identifier
|
||||
Flags int32 // Flags is the swap flags applied to the device
|
||||
@@ -98,7 +112,7 @@ type xswdev struct {
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
// FreeBSD can have multiple swap devices so we total them up
|
||||
i, err := unix.SysctlUint32("vm.nswapdev")
|
||||
i, err := common.SysctlUint("vm.nswapdev")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -109,11 +123,11 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
|
||||
c := int(i)
|
||||
|
||||
i, err = unix.SysctlUint32("vm.stats.vm.v_page_size")
|
||||
i, err = common.SysctlUint("vm.stats.vm.v_page_size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pageSize := uint64(i)
|
||||
pageSize := i
|
||||
|
||||
var buf []byte
|
||||
s := &SwapMemoryStat{}
|
||||
@@ -123,12 +137,23 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// first, try to parse with version 2
|
||||
xsw := (*xswdev)(unsafe.Pointer(&buf[0]))
|
||||
if xsw.Version != XSWDEV_VERSION {
|
||||
if xsw.Version == XSWDEV_VERSION11 {
|
||||
// this is version 1, so try to parse again
|
||||
xsw := (*xswdev11)(unsafe.Pointer(&buf[0]))
|
||||
if xsw.Version != XSWDEV_VERSION11 {
|
||||
return nil, errors.New("xswdev version mismatch(11)")
|
||||
}
|
||||
s.Total += uint64(xsw.NBlks)
|
||||
s.Used += uint64(xsw.Used)
|
||||
} else if xsw.Version != XSWDEV_VERSION {
|
||||
return nil, errors.New("xswdev version mismatch")
|
||||
} else {
|
||||
s.Total += uint64(xsw.NBlks)
|
||||
s.Used += uint64(xsw.Used)
|
||||
}
|
||||
s.Total += uint64(xsw.NBlks)
|
||||
s.Used += uint64(xsw.Used)
|
||||
|
||||
}
|
||||
|
||||
if s.Total != 0 {
|
||||
|
||||
+90
-1
@@ -4,6 +4,8 @@ package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -11,6 +13,11 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type VirtualMemoryExStat struct {
|
||||
ActiveFile uint64 `json:"activefile"`
|
||||
InactiveFile uint64 `json:"inactivefile"`
|
||||
}
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
@@ -18,10 +25,16 @@ func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
filename := common.HostProc("meminfo")
|
||||
lines, _ := common.ReadLines(filename)
|
||||
|
||||
// flag if MemAvailable is in /proc/meminfo (kernel 3.14+)
|
||||
memavail := false
|
||||
activeFile := false // "Active(file)" not available: 2.6.28 / Dec 2008
|
||||
inactiveFile := false // "Inactive(file)" not available: 2.6.28 / Dec 2008
|
||||
sReclaimable := false // "SReclaimable:" not available: 2.6.19 / Nov 2006
|
||||
|
||||
ret := &VirtualMemoryStat{}
|
||||
retEx := &VirtualMemoryExStat{}
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) != 2 {
|
||||
@@ -51,6 +64,12 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
ret.Active = t * 1024
|
||||
case "Inactive":
|
||||
ret.Inactive = t * 1024
|
||||
case "Active(file)":
|
||||
activeFile = true
|
||||
retEx.ActiveFile = t * 1024
|
||||
case "Inactive(file)":
|
||||
inactiveFile = true
|
||||
retEx.InactiveFile = t * 1024
|
||||
case "Writeback":
|
||||
ret.Writeback = t * 1024
|
||||
case "WritebackTmp":
|
||||
@@ -62,7 +81,10 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
case "Slab":
|
||||
ret.Slab = t * 1024
|
||||
case "SReclaimable":
|
||||
sReclaimable = true
|
||||
ret.SReclaimable = t * 1024
|
||||
case "SUnreclaim":
|
||||
ret.SUnreclaim = t * 1024
|
||||
case "PageTables":
|
||||
ret.PageTables = t * 1024
|
||||
case "SwapCached":
|
||||
@@ -103,8 +125,13 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
ret.Cached += ret.SReclaimable
|
||||
|
||||
if !memavail {
|
||||
ret.Available = ret.Free + ret.Buffers + ret.Cached
|
||||
if activeFile && inactiveFile && sReclaimable {
|
||||
ret.Available = calcuateAvailVmem(ret, retEx)
|
||||
} else {
|
||||
ret.Available = ret.Cached + ret.Free
|
||||
}
|
||||
}
|
||||
|
||||
ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached
|
||||
ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
|
||||
|
||||
@@ -152,7 +179,69 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
continue
|
||||
}
|
||||
ret.Sout = value * 4 * 1024
|
||||
case "pgpgin":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.PgIn = value * 4 * 1024
|
||||
case "pgpgout":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.PgOut = value * 4 * 1024
|
||||
case "pgfault":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.PgFault = value * 4 * 1024
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// calcuateAvailVmem is a fallback under kernel 3.14 where /proc/meminfo does not provide
|
||||
// "MemAvailable:" column. It reimplements an algorithm from the link below
|
||||
// https://github.com/giampaolo/psutil/pull/890
|
||||
func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 {
|
||||
var watermarkLow uint64
|
||||
|
||||
fn := common.HostProc("zoneinfo")
|
||||
lines, err := common.ReadLines(fn)
|
||||
|
||||
if err != nil {
|
||||
return ret.Free + ret.Cached // fallback under kernel 2.6.13
|
||||
}
|
||||
|
||||
pagesize := uint64(os.Getpagesize())
|
||||
watermarkLow = 0
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
|
||||
if strings.HasPrefix(fields[0], "low") {
|
||||
lowValue, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
|
||||
if err != nil {
|
||||
lowValue = 0
|
||||
}
|
||||
watermarkLow += lowValue
|
||||
}
|
||||
}
|
||||
|
||||
watermarkLow *= pagesize
|
||||
|
||||
availMemory := ret.Free - watermarkLow
|
||||
pageCache := retEx.ActiveFile + retEx.InactiveFile
|
||||
pageCache -= uint64(math.Min(float64(pageCache/2), float64(watermarkLow)))
|
||||
availMemory += pageCache
|
||||
availMemory += ret.SReclaimable - uint64(math.Min(float64(ret.SReclaimable/2.0), float64(watermarkLow)))
|
||||
|
||||
if availMemory < 0 {
|
||||
availMemory = 0
|
||||
}
|
||||
|
||||
return availMemory
|
||||
}
|
||||
|
||||
+3
-3
@@ -52,7 +52,7 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
}
|
||||
|
||||
func zoneName() (string, error) {
|
||||
zonename, err := exec.LookPath("/usr/bin/zonename")
|
||||
zonename, err := exec.LookPath("zonename")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func zoneName() (string, error) {
|
||||
var globalZoneMemoryCapacityMatch = regexp.MustCompile(`memory size: ([\d]+) Megabytes`)
|
||||
|
||||
func globalZoneMemoryCapacity() (uint64, error) {
|
||||
prtconf, err := exec.LookPath("/usr/sbin/prtconf")
|
||||
prtconf, err := exec.LookPath("prtconf")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func globalZoneMemoryCapacity() (uint64, error) {
|
||||
var kstatMatch = regexp.MustCompile(`([^\s]+)[\s]+([^\s]*)`)
|
||||
|
||||
func nonGlobalZoneMemoryCapacity() (uint64, error) {
|
||||
kstat, err := exec.LookPath("/usr/bin/kstat")
|
||||
kstat, err := exec.LookPath("kstat")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user