From 99b6e5ac251408c8215bf06190116b57690247d7 Mon Sep 17 00:00:00 2001 From: Bo Tran Date: Fri, 20 Nov 2020 01:50:43 +0700 Subject: [PATCH 1/4] add support windows and new metrics --- collector/cpu_windows.go | 176 +++++++++++++++++++++ collector/diskstats_common.go | 2 +- collector/diskstats_windows.go | 254 ++++++++++++++++++++++++++++++ collector/loadavg.go | 2 +- collector/loadavg_windows.go | 51 ++++++ collector/meminfo.go | 2 +- collector/meminfo_windows.go | 87 ++++++++++ collector/meminfo_windows_test.go | 131 +++++++++++++++ collector/netdev_common.go | 11 +- collector/netdev_windows.go | 77 +++++++++ collector/netstat_windows.go | 121 ++++++++++++++ collector/processes_windows.go | 200 +++++++++++++++++++++++ go.mod | 3 + go.sum | 6 + 14 files changed, 1117 insertions(+), 6 deletions(-) create mode 100644 collector/cpu_windows.go create mode 100644 collector/diskstats_windows.go create mode 100644 collector/loadavg_windows.go create mode 100644 collector/meminfo_windows.go create mode 100644 collector/meminfo_windows_test.go create mode 100644 collector/netdev_windows.go create mode 100644 collector/netstat_windows.go create mode 100644 collector/processes_windows.go diff --git a/collector/cpu_windows.go b/collector/cpu_windows.go new file mode 100644 index 0000000000..b2a0483634 --- /dev/null +++ b/collector/cpu_windows.go @@ -0,0 +1,176 @@ +// Copyright 2020 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. + +// +build !nocpu + +package collector + +import ( + "fmt" + "strconv" + "sync" + "time" + + "github.com/go-kit/kit/log" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/procfs" + "github.com/shirou/gopsutil/cpu" + "gopkg.in/alecthomas/kingpin.v2" +) + +type cpuCollector struct { + cpu *prometheus.Desc + cpuInfo *prometheus.Desc + cpuTimes *prometheus.Desc + cpuGuest *prometheus.Desc + perCPUPercent *prometheus.Desc + logger log.Logger + cpuStats []procfs.CPUStat + cpuStatsMutex sync.Mutex +} + +var ( + enableCPUInfo = kingpin.Flag("collector.cpu.info", "Enables metric cpu_info").Bool() +) + +func init() { + registerCollector("cpu", defaultEnabled, NewCPUCollector) +} + +// NewCPUCollector returns a new Collector exposing kernel/system statistics. +func NewCPUCollector(logger log.Logger) (Collector, error) { + return &cpuCollector{ + cpu: nodeCPUSecondsDesc, + cpuInfo: prometheus.NewDesc( + prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "info"), + "CPU information", + []string{"package", "core", "cpu", "vendor", "family", "model", "model_name", "microcode", "stepping", "cachesize"}, nil, + ), + cpuGuest: prometheus.NewDesc( + prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "guest_seconds_total"), + "Seconds the cpus spent in guests (VMs) for each mode.", + []string{"cpu", "mode"}, nil, + ), + cpuTimes: prometheus.NewDesc( + prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "per_seconds_total"), + "The amounts of time the CPU has spent performing different kinds of work.", + []string{"cpu", "mode"}, nil, + ), + perCPUPercent: prometheus.NewDesc( + prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "percentage"), + "Percent calculates the percentage of cpu used either per CPU", + []string{"cpu"}, nil, + ), + logger: logger, + }, nil +} + +// Update implements Collector and exposes cpu related metrics from /proc/stat and /sys/.../cpu/. +func (c *cpuCollector) Update(ch chan<- prometheus.Metric) error { + if *enableCPUInfo { + if err := c.updateInfo(ch); err != nil { + return err + } + } + if err := c.updateTimesStat(ch); err != nil { + return err + } + if err := c.updatePercent(ch); err != nil { + return err + } + return nil +} + +// updateInfo cpuinfo +func (c *cpuCollector) updateInfo(ch chan<- prometheus.Metric) error { + info, err := cpu.Info() + if err != nil { + return err + } + for _, cpu := range info { + ch <- prometheus.MustNewConstMetric(c.cpuInfo, + prometheus.GaugeValue, + 1, + cpu.CoreID, + strconv.Itoa(int(cpu.Cores)), + cpu.VendorID, + cpu.Family, + cpu.Model, + cpu.ModelName, + cpu.Microcode, + strconv.Itoa(int(cpu.Stepping)), + strconv.Itoa(int(cpu.CacheSize))) + } + return nil +} + +// updatePercent do percent calculates the percentage of cpu used either per CPU or combined. +// If an interval of 0 is given it will compare the current cpu times against the last call. +// Returns one value per cpu, or a single value if percpu is set to false. +func (c *cpuCollector) updatePercent(ch chan<- prometheus.Metric) error { + percents, err := cpu.Percent(time.Duration(1)*time.Second, true) + if err != nil { + return err + } + + var allCPU float64 + for cpuID, percent := range percents { + ch <- prometheus.MustNewConstMetric(c.perCPUPercent, prometheus.CounterValue, percent, fmt.Sprintf("cpu%v", cpuID)) + allCPU += percent + } + ch <- prometheus.MustNewConstMetric(c.perCPUPercent, prometheus.CounterValue, allCPU/float64(len(percents)), "cpu") + + return nil +} + +// updateStat exports cpu related metrics. +func (c *cpuCollector) updateTimesStat(ch chan<- prometheus.Metric) error { + c.cpuStatsMutex.Lock() + defer c.cpuStatsMutex.Unlock() + + cpuStats, err := cpu.Times(true) + if err != nil { + return err + } + for cpuID, cpuStat := range cpuStats { + cpuNum := strconv.Itoa(cpuID) + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.User, cpuNum, "user") + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.Nice, cpuNum, "nice") + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.System, cpuNum, "system") + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.Idle, cpuNum, "idle") + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.Iowait, cpuNum, "iowait") + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.Irq, cpuNum, "irq") + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.Softirq, cpuNum, "softirq") + ch <- prometheus.MustNewConstMetric(c.cpuTimes, prometheus.CounterValue, cpuStat.Steal, cpuNum, "steal") + } + + totalCPUStat, err := cpu.Times(false) + if err != nil { + return err + } + for _, total := range totalCPUStat { + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.User, total.CPU, "user") + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.Nice, total.CPU, "nice") + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.System, total.CPU, "system") + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.Idle, total.CPU, "idle") + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.Iowait, total.CPU, "iowait") + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.Irq, total.CPU, "irq") + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.Softirq, total.CPU, "softirq") + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, total.Steal, total.CPU, "steal") + + // Guest CPU is also accounted for in cpuStat.User and cpuStat.Nice, expose these as separate metrics. + ch <- prometheus.MustNewConstMetric(c.cpuGuest, prometheus.CounterValue, total.Guest, total.CPU, "user") + ch <- prometheus.MustNewConstMetric(c.cpuGuest, prometheus.CounterValue, total.GuestNice, total.CPU, "nice") + } + return nil +} diff --git a/collector/diskstats_common.go b/collector/diskstats_common.go index 7efb399a38..3a6f5bf2cd 100644 --- a/collector/diskstats_common.go +++ b/collector/diskstats_common.go @@ -12,7 +12,7 @@ // limitations under the License. // +build !nodiskstats -// +build openbsd linux darwin +// +build openbsd linux darwin windows package collector diff --git a/collector/diskstats_windows.go b/collector/diskstats_windows.go new file mode 100644 index 0000000000..7686c1b117 --- /dev/null +++ b/collector/diskstats_windows.go @@ -0,0 +1,254 @@ +// Copyright 2020 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. + +// +build !nodiskstats + +package collector + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "github.com/prometheus/client_golang/prometheus" + "github.com/shirou/gopsutil/disk" + "gopkg.in/alecthomas/kingpin.v2" +) + +const ( + diskSectorSize = 512 +) + +var ( + ignoredDevices = kingpin.Flag("collector.diskstats.ignored-devices", "Regexp of devices to ignore for diskstats.").Default("^(ram|loop|fd|(h|s|v|xv)d[a-z]|nvme\\d+n\\d+p)\\d+$").String() +) + +type typedFactorDesc struct { + desc *prometheus.Desc + valueType prometheus.ValueType + factor float64 +} + +func (d *typedFactorDesc) mustNewConstMetric(value float64, labels ...string) prometheus.Metric { + if d.factor != 0 { + value *= d.factor + } + return prometheus.MustNewConstMetric(d.desc, d.valueType, value, labels...) +} + +type diskstatsCollector struct { + ignoredDevicesPattern *regexp.Regexp + descs []typedFactorDesc + descUsages []typedFactorDesc + logger log.Logger +} + +func init() { + registerCollector("diskstats", defaultEnabled, NewDiskstatsCollector) +} + +// NewDiskstatsCollector returns a new Collector exposing disk device stats. +// Docs from https://www.kernel.org/doc/Documentation/iostats.txt +func NewDiskstatsCollector(logger log.Logger) (Collector, error) { + var diskLabelNames = []string{"device"} + + return &diskstatsCollector{ + ignoredDevicesPattern: regexp.MustCompile(*ignoredDevices), + descs: []typedFactorDesc{ + { + desc: readsCompletedDesc, valueType: prometheus.CounterValue, + }, + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "reads_merged_total"), + "The total number of reads merged.", + diskLabelNames, + nil, + ), valueType: prometheus.CounterValue, + }, + { + desc: readBytesDesc, valueType: prometheus.CounterValue, + factor: diskSectorSize, + }, + { + desc: readTimeSecondsDesc, valueType: prometheus.CounterValue, + factor: .001, + }, + { + desc: writesCompletedDesc, valueType: prometheus.CounterValue, + }, + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "writes_merged_total"), + "The number of writes merged.", + diskLabelNames, + nil, + ), valueType: prometheus.CounterValue, + }, + { + desc: writtenBytesDesc, valueType: prometheus.CounterValue, + factor: diskSectorSize, + }, + { + desc: writeTimeSecondsDesc, valueType: prometheus.CounterValue, + factor: .001, + }, + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "io_now"), + "The number of I/Os currently in progress.", + diskLabelNames, + nil, + ), valueType: prometheus.GaugeValue, + }, + { + desc: ioTimeSecondsDesc, valueType: prometheus.CounterValue, + factor: .001, + }, + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "io_time_weighted_seconds_total"), + "The weighted # of seconds spent doing I/Os.", + diskLabelNames, + nil, + ), valueType: prometheus.CounterValue, + factor: .001, + }, + }, + descUsages: []typedFactorDesc{ + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "total_bytes"), + "The total of disk.", + diskLabelNames, + nil, + ), valueType: prometheus.CounterValue, + }, + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "free_bytes"), + "The free of disk", + diskLabelNames, + nil, + ), valueType: prometheus.CounterValue, + }, + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "used_bytes"), + "The used in disk.", + diskLabelNames, + nil, + ), valueType: prometheus.CounterValue, + }, + { + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, diskSubsystem, "usedPercent"), + "The percentage used in disk.", + diskLabelNames, + nil, + ), valueType: prometheus.CounterValue, + factor: .001, + }, + }, + logger: logger, + }, nil +} + +func (c *diskstatsCollector) Update(ch chan<- prometheus.Metric) error { + diskIOCounters, err := getDiskIOCounters() + if err != nil { + return fmt.Errorf("couldn't get diskstats: %s", err) + } + + for dev, stats := range diskIOCounters { + if c.ignoredDevicesPattern.MatchString(dev) { + level.Debug(c.logger).Log("msg", "Ignoring device", "device", dev) + continue + } + + for i, value := range stats { + v, err := strconv.ParseFloat(value, 64) + if err != nil { + return fmt.Errorf("invalid value %s in diskstats: %s", value, err) + } + ch <- c.descs[i].mustNewConstMetric(v, dev) + } + } + + diskUsages, err := getDiskUsages() + if err != nil { + return fmt.Errorf("couldn't get disk usages: %s", err) + } + for dev, stats := range diskUsages { + if c.ignoredDevicesPattern.MatchString(dev) { + level.Debug(c.logger).Log("msg", "Ignoring device", "device", dev) + continue + } + + for i, value := range stats { + v, err := strconv.ParseFloat(value, 64) + if err != nil { + return fmt.Errorf("invalid value %s in diskstats: %s", value, err) + } + ch <- c.descUsages[i].mustNewConstMetric(v, dev) + } + } + return nil +} + +func getDiskIOCounters() (map[string][]string, error) { + diskIOCounters := map[string][]string{} + diskIOCs, err := disk.IOCounters() + if err != nil { + return diskIOCounters, err + } + for dev, diskIOC := range diskIOCs { + diskIOCounters[dev] = []string{ + fmt.Sprintf(`%v`, diskIOC.ReadCount), + fmt.Sprintf(`%v`, diskIOC.MergedReadCount), + fmt.Sprintf(`%v`, diskIOC.ReadBytes), + fmt.Sprintf(`%v`, diskIOC.ReadTime), + fmt.Sprintf(`%v`, diskIOC.WriteCount), + fmt.Sprintf(`%v`, diskIOC.MergedWriteCount), + fmt.Sprintf(`%v`, diskIOC.WriteBytes), + fmt.Sprintf(`%v`, diskIOC.WriteTime), + fmt.Sprintf(`%v`, diskIOC.IopsInProgress), + fmt.Sprintf(`%v`, diskIOC.IoTime), + fmt.Sprintf(`%v`, diskIOC.WeightedIO), + } + } + return diskIOCounters, nil +} + +func getDiskUsages() (map[string][]string, error) { + diskUsages := map[string][]string{} + partitions, err := disk.Partitions(true) + if err != nil { + return diskUsages, err + } + for _, partition := range partitions { + devUsages, err := disk.Usage(partition.Mountpoint) + if err != nil { + return diskUsages, err + } + diskUsages[partition.Mountpoint] = []string{ + fmt.Sprintf(`%v`, devUsages.Total), + fmt.Sprintf(`%v`, devUsages.Free), + fmt.Sprintf(`%v`, devUsages.Used), + fmt.Sprintf("%.6f", devUsages.UsedPercent), + } + } + return diskUsages, nil +} diff --git a/collector/loadavg.go b/collector/loadavg.go index 7c1fd99603..bc1fcbcfb2 100644 --- a/collector/loadavg.go +++ b/collector/loadavg.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build darwin dragonfly freebsd linux netbsd openbsd solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows // +build !noloadavg package collector diff --git a/collector/loadavg_windows.go b/collector/loadavg_windows.go new file mode 100644 index 0000000000..c2d8b4576b --- /dev/null +++ b/collector/loadavg_windows.go @@ -0,0 +1,51 @@ +// Copyright 2020 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. + +// +build !noloadavg + +package collector + +import ( + "encoding/json" + "fmt" + + "github.com/shirou/gopsutil/load" +) + +// Read loadavg +func getLoad() (loads []float64, err error) { + data, err := load.Avg() + if err != nil { + return nil, err + } + loads, err = parseLoad(data) + if err != nil { + return nil, err + } + return loads, nil +} + +// Parse loadavg and return 1m, 5m and 15m. +func parseLoad(data *load.AvgStat) (loads []float64, err error) { + loads = make([]float64, 0) + info := make(map[string]float64) + loadavg, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("could not parse load: %s", err) + } + json.Unmarshal(loadavg, &info) + for _, v := range info { + loads = append(loads, v) + } + return loads, nil +} diff --git a/collector/meminfo.go b/collector/meminfo.go index 38b2326883..1eac0be152 100644 --- a/collector/meminfo.go +++ b/collector/meminfo.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build darwin linux openbsd +// +build darwin linux openbsd windows // +build !nomeminfo package collector diff --git a/collector/meminfo_windows.go b/collector/meminfo_windows.go new file mode 100644 index 0000000000..11653bb901 --- /dev/null +++ b/collector/meminfo_windows.go @@ -0,0 +1,87 @@ +// Copyright 2020 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. + +// +build !nomeminfo + +package collector + +import ( + "encoding/json" + "regexp" + + "github.com/shirou/gopsutil/mem" +) + +var ( + reParens = regexp.MustCompile(`\((.*)\)`) +) + +func parseMemInfo(v, s interface{}) error { + byteMemInfo, err := json.Marshal(v) + if err != nil { + return err + } + + err = json.Unmarshal(byteMemInfo, &s) + if err != nil { + return err + } + + return nil +} + +func mergeInfo(infors, s map[string]float64, prefix string) error { + // Merged values + for k, v := range infors { + // change fields name + if k == "usedPercent" { + k = prefix + "_used_percent" + } else { + k = prefix + "_" + k + "_bytes" + } + + s[k] = v + } + return nil +} + +func (c *meminfoCollector) getMemInfo() (map[string]float64, error) { + info := make(map[string]float64) + // Get swap memory stats + swapInfo := make(map[string]float64) + swapStats, err := mem.SwapMemory() + if err != nil { + return info, err + } + err = parseMemInfo(swapStats, &swapInfo) + if err != nil { + return info, err + } + + // Get virtual memory stats + virtualInfo := make(map[string]float64) + virtualStats, err := mem.VirtualMemory() + if err != nil { + return info, err + } + err = parseMemInfo(virtualStats, &virtualInfo) + if err != nil { + return info, err + } + + // Merged values + mergeInfo(swapInfo, info, "swap") + mergeInfo(virtualInfo, info, "virtual") + + return info, nil +} diff --git a/collector/meminfo_windows_test.go b/collector/meminfo_windows_test.go new file mode 100644 index 0000000000..1c5b409705 --- /dev/null +++ b/collector/meminfo_windows_test.go @@ -0,0 +1,131 @@ +// Copyright 2020 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. + +// +build !nomeminfo + +package collector + +import ( + "reflect" + "testing" +) + +// var ( +// reParens = regexp.MustCompile(`\((.*)\)`) +// ) + +func TestGetMemInfo(t *testing.T) { + info := make(map[string]float64) + swapInfo := map[string]float64{ + "total": 1024454656, + "used": 1060864, + "free": 1023393792, + "usedPercent": 0.10355402201422569, + "sin": 36864, + "sout": 81920, + "pgin": 27170140160, + "pgout": 210821484544, + "pgfault": 10854351642624, + } + + virtualInfo := map[string]float64{ + "total": 16705363968, + "available": 5313527808, + "used": 10251444224, + "usedPercent": 61.366183003478284, + "free": 1512931328, + "active": 11915513856, + "inactive": 2571345920, + "wired": 0, + "laundry": 0, + "buffers": 459743232, + "cached": 4481245184, + "writeback": 106496, + "dirty": 892928, + "writebacktmp": 0, + "shared": 909332480, + "slab": 454131712, + "sreclaimable": 318140416, + "sunreclaim": 135991296, + "pagetables": 113631232, + "swapcached": 8192, + "commitlimit": 9377136640, + "committedas": 23042527232, + "hightotal": 0, + "highfree": 0, + "lowtotal": 0, + "lowfree": 0, + "swaptotal": 1024454656, + "swapfree": 1023393792, + "mapped": 1528872960, + "vmalloctotal": 35184372087808, + "vmallocused": 0, + "vmallocchunk": 0, + "hugepagestotal": 0, + "hugepagesfree": 0, + "hugepagesize": 2097152, + } + + mergeInfo(swapInfo, info, "swap") + mergeInfo(virtualInfo, info, "virtual") + + expectedInfo := map[string]float64{ + "swap_free_bytes": 1.023393792e+09, + "swap_pgfault_bytes": 1.0854351642624e+13, + "swap_pgin_bytes": 2.717014016e+10, + "swap_pgout_bytes": 2.10821484544e+11, + "swap_sin_bytes": 36864, + "swap_sout_bytes": 81920, + "swap_total_bytes": 1.024454656e+09, + "swap_used_bytes": 1.060864e+06, + "swap_used_percent": 0.10355402201422569, + "virtual_active_bytes": 1.1915513856e+10, + "virtual_available_bytes": 5.313527808e+09, + "virtual_buffers_bytes": 4.59743232e+08, + "virtual_cached_bytes": 4.481245184e+09, + "virtual_commitlimit_bytes": 9.37713664e+09, + "virtual_committedas_bytes": 2.3042527232e+10, + "virtual_dirty_bytes": 892928, + "virtual_free_bytes": 1.512931328e+09, + "virtual_highfree_bytes": 0, + "virtual_hightotal_bytes": 0, + "virtual_hugepagesfree_bytes": 0, + "virtual_hugepagesize_bytes": 2.097152e+06, + "virtual_hugepagestotal_bytes": 0, + "virtual_inactive_bytes": 2.57134592e+09, + "virtual_laundry_bytes": 0, + "virtual_lowfree_bytes": 0, + "virtual_lowtotal_bytes": 0, + "virtual_mapped_bytes": 1.52887296e+09, + "virtual_pagetables_bytes": 1.13631232e+08, + "virtual_shared_bytes": 9.0933248e+08, + "virtual_slab_bytes": 4.54131712e+08, + "virtual_sreclaimable_bytes": 3.18140416e+08, + "virtual_sunreclaim_bytes": 1.35991296e+08, + "virtual_swapcached_bytes": 8192, + "virtual_swapfree_bytes": 1.023393792e+09, + "virtual_swaptotal_bytes": 1.024454656e+09, + "virtual_total_bytes": 1.6705363968e+10, + "virtual_used_bytes": 1.0251444224e+10, + "virtual_used_percent": 61.366183003478284, + "virtual_vmallocchunk_bytes": 0, + "virtual_vmalloctotal_bytes": 3.5184372087808e+13, + "virtual_vmallocused_bytes": 0, + "virtual_wired_bytes": 0, + "virtual_writeback_bytes": 106496, + "virtual_writebacktmp_bytes": 0, + } + if !reflect.DeepEqual(info, expectedInfo) { + t.Error("want: ", expectedInfo, "but got: ", info) + } +} diff --git a/collector/netdev_common.go b/collector/netdev_common.go index 7b54c47273..4733d75476 100644 --- a/collector/netdev_common.go +++ b/collector/netdev_common.go @@ -12,7 +12,7 @@ // limitations under the License. // +build !nonetdev -// +build linux freebsd openbsd dragonfly darwin +// +build linux freebsd openbsd dragonfly darwin windows package collector @@ -20,6 +20,7 @@ import ( "errors" "fmt" "regexp" + "strconv" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" @@ -30,7 +31,7 @@ import ( var ( netdevDeviceInclude = kingpin.Flag("collector.netdev.device-include", "Regexp of net devices to include (mutually exclusive to device-exclude).").String() oldNetdevDeviceInclude = kingpin.Flag("collector.netdev.device-whitelist", "DEPRECATED: Use collector.netdev.device-include").Hidden().String() - netdevDeviceExclude = kingpin.Flag("collector.netdev.device-exclude", "Regexp of net devices to exclude (mutually exclusive to device-include).").String() + netdevDeviceExclude = kingpin.Flag("collector.netdev.device-exclude", "Regexp of net devices to exclude (mutually exclusive to device-include).").Default("^(Teredo|Reusable|isatap.|Loopback)").String() oldNetdevDeviceExclude = kingpin.Flag("collector.netdev.device-blacklist", "DEPRECATED: Use collector.netdev.device-exclude").Hidden().String() ) @@ -110,7 +111,11 @@ func (c *netDevCollector) Update(ch chan<- prometheus.Metric) error { ) c.metricDescs[key] = desc } - ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, float64(value), dev) + v, err := strconv.ParseFloat(value, 64) + if err != nil { + return fmt.Errorf("invalid value %s in netstats: %s", value, err) + } + ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, dev) } } return nil diff --git a/collector/netdev_windows.go b/collector/netdev_windows.go new file mode 100644 index 0000000000..9fd39c067a --- /dev/null +++ b/collector/netdev_windows.go @@ -0,0 +1,77 @@ +// Copyright 2020 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. + +// +build !nonetdev + +package collector + +import ( + "encoding/json" + "regexp" + "strconv" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "github.com/shirou/gopsutil/net" +) + +func getNetDevStats(ignore *regexp.Regexp, accept *regexp.Regexp, logger log.Logger) (map[string]map[string]string, error) { + netInterfaces, err := net.IOCounters(true) + if err != nil { + return nil, err + } + + return parseNetDevStats(netInterfaces, ignore, accept, logger) +} + +func parseNetDevStats(ni []net.IOCountersStat, ignore *regexp.Regexp, accept *regexp.Regexp, logger log.Logger) (map[string]map[string]string, error) { + + netDev := map[string]map[string]string{} + + for _, net := range ni { + dev := net.Name + if ignore != nil && ignore.MatchString(dev) { + level.Debug(logger).Log("msg", "Ignoring device", "device", dev) + continue + } + if accept != nil && !accept.MatchString(dev) { + level.Debug(logger).Log("msg", "Ignoring device", "device", dev) + continue + } + statistic, err := parseToString(net) + if err != nil { + return nil, err + } + netDev[dev] = statistic + } + return netDev, nil +} + +func parseToString(data net.IOCountersStat) (map[string]string, error) { + statistic := make(map[string]uint64) + resultParsed := make(map[string]string) + + statsBytes, err := json.Marshal(data) + if err != nil { + return nil, err + } + json.Unmarshal(statsBytes, &statistic) + + // Ignore field name in statistic map + delete(statistic, "name") + + for k, v := range statistic { + resultParsed[k] = strconv.FormatUint(v, 10) + } + return resultParsed, nil +} diff --git a/collector/netstat_windows.go b/collector/netstat_windows.go new file mode 100644 index 0000000000..0f9155ff0f --- /dev/null +++ b/collector/netstat_windows.go @@ -0,0 +1,121 @@ +// Copyright 2020 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. + +// +build !nonetstat + +package collector + +import ( + "fmt" + "regexp" + + "github.com/go-kit/kit/log" + "github.com/prometheus/client_golang/prometheus" + "github.com/shirou/gopsutil/net" + "gopkg.in/alecthomas/kingpin.v2" +) + +const ( + netStatsSubsystem = "netstat" +) + +var ( + netStatFields = kingpin.Flag("collector.netstat.fields", "Regexp of fields to return for netstat collector.").Default("(all|tcp|tcp4|tcp6|udp|udp4|udp6|inet|inet4|inet6)$").String() +) + +type netStatCollector struct { + fieldPattern *regexp.Regexp + logger log.Logger +} + +func init() { + registerCollector("netstat", defaultEnabled, NewNetStatCollector) +} + +// NewNetStatCollector takes and returns +// a new Collector exposing network stats. +func NewNetStatCollector(logger log.Logger) (Collector, error) { + pattern := regexp.MustCompile(*netStatFields) + return &netStatCollector{ + fieldPattern: pattern, + logger: logger, + }, nil +} + +func (c *netStatCollector) Update(ch chan<- prometheus.Metric) error { + netStats, err := getNetStats() + if err != nil { + return fmt.Errorf("couldn't get netstats: %s", err) + } + for protocol, protocolStats := range netStats { + if !c.fieldPattern.MatchString(protocol) { + continue + } + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, netStatsSubsystem, protocol), + fmt.Sprintf("Statistic connections %s.", protocol), + nil, nil, + ), + prometheus.CounterValue, float64(protocolStats), + ) + } + return nil +} + +func getNetStats() (map[string]int, error) { + netStats := make(map[string]int) + allNetStats, err := net.Connections("all") + if err != nil { + return nil, err + } + netStats["all"] = len(allNetStats) + + // Get connections tcp + tcpNetStats, _ := net.Connections("tcp") + netStats["tcp"] = len(tcpNetStats) + + // Get connections tcp4 + tcp4NetStats, _ := net.Connections("tcp4") + netStats["tcp4"] = len(tcp4NetStats) + + // Get connections tcp6 + tcp6NetStats, _ := net.Connections("tcp6") + netStats["tcp6"] = len(tcp6NetStats) + + // Get connections udp + udpNetStats, _ := net.Connections("udp") + netStats["udp"] = len(udpNetStats) + + // Get connections udp4 + udp4NetStats, _ := net.Connections("udp4") + netStats["udp4"] = len(udp4NetStats) + + // Get connections udp6 + udp6NetStats, _ := net.Connections("udp6") + netStats["udp6"] = len(udp6NetStats) + + // Get connections inet + inetNetStats, _ := net.Connections("inet") + netStats["inet"] = len(inetNetStats) + + // Get connections inet4 + inet4NetStats, _ := net.Connections("inet4") + netStats["inet4"] = len(inet4NetStats) + + // Get connections inet6 + inet6NetStats, _ := net.Connections("inet6") + netStats["inet6"] = len(inet6NetStats) + + return netStats, nil +} diff --git a/collector/processes_windows.go b/collector/processes_windows.go new file mode 100644 index 0000000000..c73f8061c0 --- /dev/null +++ b/collector/processes_windows.go @@ -0,0 +1,200 @@ +// Copyright 2020 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. + +// +build !noprocesses + +package collector + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/go-kit/kit/log" + "github.com/prometheus/client_golang/prometheus" + "github.com/shirou/gopsutil/process" +) + +const ( + subsystem = "processes" +) + +var ( + // Remake labels + r = strings.NewReplacer(".exe", "", "-", "_", ".", "_") +) + +type processCollector struct { + procfs []*process.Process + pidUsed *prometheus.Desc + logger log.Logger +} + +func init() { + registerCollector("processes", defaultDisabled, NewProcessStatCollector) +} + +// NewProcessStatCollector returns a new Collector exposing process data read from the proc filesystem. +func NewProcessStatCollector(logger log.Logger) (Collector, error) { + pfs, err := process.Processes() + if err != nil { + return nil, fmt.Errorf("failed to get procfs: %w", err) + } + return &processCollector{ + procfs: pfs, + pidUsed: prometheus.NewDesc(prometheus.BuildFQName(namespace, subsystem, "pids"), + "Number of PIDs", nil, nil, + ), + logger: logger, + }, nil +} +func (c *processCollector) Update(ch chan<- prometheus.Metric) error { + // handle error is not required + // We need update process + c.procfs, _ = process.Processes() + + // Update number pids + ch <- prometheus.MustNewConstMetric(c.pidUsed, prometheus.GaugeValue, float64(len(c.procfs))) + + procStats, err := c.getAllocatedProcesses() + if err != nil { + return fmt.Errorf("unable to retrieve number of allocated processes: %q", err) + } + + for procName, procInfo := range procStats { + // Update memory information + for k, v := range procInfo["mem"] { + var key = "mem_bytes" + if k == "used_percent" { + key = "mem_percent" + } + + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, key), + "Memory information field kinds of processes.", + []string{"process_name", "parameter_name"}, nil, + ), + prometheus.GaugeValue, v, procName, k, + ) + } + + // Update iocounters information + for k, v := range procInfo["iocounters"] { + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "iocounters_bytes"), + fmt.Sprintf("IOCounters information field kinds of processes."), + []string{"process_name", "parameter_name"}, nil, + ), + prometheus.GaugeValue, v, procName, k, + ) + } + } + return nil +} + +func (c *processCollector) getAllocatedProcesses() (map[string]map[string]map[string]float64, error) { + gaProcesses := map[string]map[string]map[string]float64{} + + for _, process := range c.procfs { + + // Ignore root pid + if process.Pid == 0 { + continue + } + + // Ignore process can't get name + pName, err := process.Name() + if err != nil { + continue + } + + // remove post.fix .exe before save to map + // get memory info that process using + kName := r.Replace(pName) + + // Make a map store data + memInfo, err := getMemoryInfo(process) + if err != nil { + continue + } + + iocInfo, err := getIOCountersInfo(process) + if err != nil { + continue + } + + // Because processes have multiple threads + // So we must counter with sum + if _, ok := gaProcesses[kName]; !ok { + // Make a new map store data + gaProcesses[kName] = map[string]map[string]float64{} + + gaProcesses[kName]["mem"] = memInfo + gaProcesses[kName]["iocounters"] = iocInfo + } else { + for k, v := range memInfo { + gaProcesses[kName]["mem"][k] = gaProcesses[kName]["mem"][k] + v + } + + for k, v := range iocInfo { + gaProcesses[kName]["iocounters"][k] = gaProcesses[kName]["iocounters"][k] + v + } + } + + } + + return gaProcesses, nil +} + +func getMemoryInfo(proc *process.Process) (map[string]float64, error) { + gMI := map[string]float64{} + memInfo, err := proc.MemoryInfo() + if err != nil { + return nil, err + } + + // parse data + memBytes, err := json.Marshal(memInfo) + if err != nil { + return nil, err + } + json.Unmarshal(memBytes, &gMI) + + // get memory percent + memPerc, err := proc.MemoryPercent() + if err != nil { + return nil, err + } + gMI["used_percent"] = float64(memPerc) + return gMI, nil + +} + +func getIOCountersInfo(proc *process.Process) (map[string]float64, error) { + gIOC := map[string]float64{} + iocInfo, err := proc.IOCounters() + if err != nil { + return nil, err + } + + // parse data + iocBytes, err := json.Marshal(iocInfo) + if err != nil { + return nil, err + } + json.Unmarshal(iocBytes, &gIOC) + + return gIOC, nil +} diff --git a/go.mod b/go.mod index 8893ce3d36..e29a2a0bc8 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,12 @@ module github.com/prometheus/node_exporter require ( + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/beevik/ntp v0.3.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/ema/qdisc v0.0.0-20200603082823-62d0308e3e00 github.com/go-kit/kit v0.10.0 + github.com/go-ole/go-ole v1.2.4 // indirect github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968 github.com/hodgesds/perf-utils v0.0.8 github.com/lufia/iostat v1.1.0 @@ -17,6 +19,7 @@ require ( github.com/prometheus/client_model v0.2.0 github.com/prometheus/common v0.14.0 github.com/prometheus/procfs v0.2.0 + github.com/shirou/gopsutil v3.20.10+incompatible github.com/siebenmann/go-kstat v0.0.0-20200303194639-4e8294f9e9d5 github.com/soundcloud/go-runit v0.0.0-20150630195641-06ad41a06c4a go.uber.org/multierr v1.5.0 // indirect diff --git a/go.sum b/go.sum index fe1a9550da..994bd5e6b9 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= @@ -80,6 +82,8 @@ github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80n github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -300,6 +304,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v3.20.10+incompatible h1:kQuRhh6h6y4luXvnmtu/lJEGtdJ3q8lbu9NQY99GP+o= +github.com/shirou/gopsutil v3.20.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/siebenmann/go-kstat v0.0.0-20200303194639-4e8294f9e9d5 h1:rRF7gJ7t0E1bfqNLwMqgb59eb273kgi+GgLE/yEiDzs= github.com/siebenmann/go-kstat v0.0.0-20200303194639-4e8294f9e9d5/go.mod h1:G81aIFAMS9ECrwBYR9YxhlPjWgrItd+Kje78O6+uqm8= From 58c8aa4fe61e9a3ed6d6d85730fd21b724a88067 Mon Sep 17 00:00:00 2001 From: Bo Tran Date: Thu, 17 Dec 2020 16:44:34 +0700 Subject: [PATCH 2/4] ignore lxcfs and tmpfs in filesystem_linux collector --- collector/filesystem_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector/filesystem_linux.go b/collector/filesystem_linux.go index 00a7323154..9e802997f9 100644 --- a/collector/filesystem_linux.go +++ b/collector/filesystem_linux.go @@ -33,7 +33,7 @@ import ( const ( defIgnoredMountPoints = "^/(dev|proc|sys|var/lib/docker/.+)($|/)" - defIgnoredFSTypes = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$" + defIgnoredFSTypes = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|fuse.lxcfs|hugetlbfs|iso9660|lxcfs|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs|tmpfs)$" ) var mountTimeout = kingpin.Flag("collector.filesystem.mount-timeout", From 246646335fd923e06eb1c15619de9b0c0c9a2344 Mon Sep 17 00:00:00 2001 From: Bo Tran Date: Wed, 23 Dec 2020 10:20:10 +0700 Subject: [PATCH 3/4] fix error cant parse unit64 to float from strconv --- collector/netdev_common.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/collector/netdev_common.go b/collector/netdev_common.go index 4733d75476..0a40a5efcb 100644 --- a/collector/netdev_common.go +++ b/collector/netdev_common.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" "regexp" - "strconv" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" @@ -111,11 +110,7 @@ func (c *netDevCollector) Update(ch chan<- prometheus.Metric) error { ) c.metricDescs[key] = desc } - v, err := strconv.ParseFloat(value, 64) - if err != nil { - return fmt.Errorf("invalid value %s in netstats: %s", value, err) - } - ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, dev) + ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, float64(value), dev) } } return nil From 9e2b7dbe754ba11381a4087a27bf7d1c928b5a90 Mon Sep 17 00:00:00 2001 From: Bo Tran Date: Tue, 26 Jan 2021 13:25:11 +0700 Subject: [PATCH 4/4] fix error can't parse float and recover code support processes for linux --- collector/processes_linux.go | 162 +++++++++++++++++++++++++++++++++-- 1 file changed, 155 insertions(+), 7 deletions(-) diff --git a/collector/processes_linux.go b/collector/processes_linux.go index 3d4e95d242..01148419a0 100644 --- a/collector/processes_linux.go +++ b/collector/processes_linux.go @@ -16,18 +16,30 @@ package collector import ( - "errors" + "encoding/json" "fmt" "os" + "strings" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/procfs" + "github.com/shirou/gopsutil/process" +) + +const ( + subsystem = "processes" +) + +var ( + // Remake labels + r = strings.NewReplacer(".exe", "", "-", "_", ".", "_") ) type processCollector struct { fs procfs.FS + procfs []*process.Process threadAlloc *prometheus.Desc threadLimit *prometheus.Desc procsState *prometheus.Desc @@ -46,9 +58,14 @@ func NewProcessStatCollector(logger log.Logger) (Collector, error) { if err != nil { return nil, fmt.Errorf("failed to open procfs: %w", err) } - subsystem := "processes" + + pfs, err := process.Processes() + if err != nil { + return nil, fmt.Errorf("failed to get procfs: %w", err) + } return &processCollector{ - fs: fs, + fs: fs, + procfs: pfs, threadAlloc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "threads"), "Allocated threads in system", @@ -76,13 +93,13 @@ func NewProcessStatCollector(logger log.Logger) (Collector, error) { func (c *processCollector) Update(ch chan<- prometheus.Metric) error { pids, states, threads, err := c.getAllocatedThreads() if err != nil { - return fmt.Errorf("unable to retrieve number of allocated threads: %w", err) + return fmt.Errorf("unable to retrieve number of allocated threads: %q", err) } ch <- prometheus.MustNewConstMetric(c.threadAlloc, prometheus.GaugeValue, float64(threads)) maxThreads, err := readUintFromFile(procFilePath("sys/kernel/threads-max")) if err != nil { - return fmt.Errorf("unable to retrieve limit number of threads: %w", err) + return fmt.Errorf("unable to retrieve limit number of threads: %q", err) } ch <- prometheus.MustNewConstMetric(c.threadLimit, prometheus.GaugeValue, float64(maxThreads)) @@ -92,11 +109,47 @@ func (c *processCollector) Update(ch chan<- prometheus.Metric) error { pidM, err := readUintFromFile(procFilePath("sys/kernel/pid_max")) if err != nil { - return fmt.Errorf("unable to retrieve limit number of maximum pids alloved: %w", err) + return fmt.Errorf("unable to retrieve limit number of maximum pids alloved: %q", err) } ch <- prometheus.MustNewConstMetric(c.pidUsed, prometheus.GaugeValue, float64(pids)) ch <- prometheus.MustNewConstMetric(c.pidMax, prometheus.GaugeValue, float64(pidM)) + // Collect metrics of processes + procStats, err := c.getAllocatedProcesses() + if err != nil { + return fmt.Errorf("unable to retrieve number of allocated processes: %q", err) + } + + for procName, procInfo := range procStats { + // Update memory information + for k, v := range procInfo["mem"] { + var key = "mem_bytes" + if k == "used_percent" { + key = "mem_percent" + } + + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, key), + "Memory information field kinds of processes.", + []string{"process_name", "parameter_name"}, nil, + ), + prometheus.GaugeValue, v, procName, k, + ) + } + + // Update iocounters information + for k, v := range procInfo["iocounters"] { + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "iocounters_bytes"), + fmt.Sprintf("IOCounters information field kinds of processes."), + []string{"process_name", "parameter_name"}, nil, + ), + prometheus.GaugeValue, v, procName, k, + ) + } + } return nil } @@ -111,7 +164,7 @@ func (c *processCollector) getAllocatedThreads() (int, map[string]int32, int, er for _, pid := range p { stat, err := pid.Stat() // PIDs can vanish between getting the list and getting stats. - if errors.Is(err, os.ErrNotExist) { + if os.IsNotExist(err) { level.Debug(c.logger).Log("msg", "file not found when retrieving stats for pid", "pid", pid, "err", err) continue } @@ -125,3 +178,98 @@ func (c *processCollector) getAllocatedThreads() (int, map[string]int32, int, er } return pids, procStates, thread, nil } + +func (c *processCollector) getAllocatedProcesses() (map[string]map[string]map[string]float64, error) { + gaProcesses := map[string]map[string]map[string]float64{} + + for _, process := range c.procfs { + + // Ignore root pid + if process.Pid == 0 { + continue + } + + // Ignore process can't get name + pName, err := process.Name() + if err != nil { + continue + } + + // remove post.fix .exe before save to map + // get memory info that process using + kName := r.Replace(pName) + + // Make a map store data + memInfo, err := getMemoryInfo(process) + if err != nil { + continue + } + + iocInfo, err := getIOCountersInfo(process) + if err != nil { + continue + } + + // Because processes have multiple threads + // So we must counter with sum + if _, ok := gaProcesses[kName]; !ok { + // Make a new map store data + gaProcesses[kName] = map[string]map[string]float64{} + + gaProcesses[kName]["mem"] = memInfo + gaProcesses[kName]["iocounters"] = iocInfo + } else { + for k, v := range memInfo { + gaProcesses[kName]["mem"][k] = gaProcesses[kName]["mem"][k] + v + } + + for k, v := range iocInfo { + gaProcesses[kName]["iocounters"][k] = gaProcesses[kName]["iocounters"][k] + v + } + } + + } + + return gaProcesses, nil +} + +func getMemoryInfo(proc *process.Process) (map[string]float64, error) { + gMI := map[string]float64{} + memInfo, err := proc.MemoryInfo() + if err != nil { + return nil, err + } + + // parse data + memBytes, err := json.Marshal(memInfo) + if err != nil { + return nil, err + } + json.Unmarshal(memBytes, &gMI) + + // get memory percent + memPerc, err := proc.MemoryPercent() + if err != nil { + return nil, err + } + gMI["used_percent"] = float64(memPerc) + return gMI, nil + +} + +func getIOCountersInfo(proc *process.Process) (map[string]float64, error) { + gIOC := map[string]float64{} + iocInfo, err := proc.IOCounters() + if err != nil { + return nil, err + } + + // parse data + iocBytes, err := json.Marshal(iocInfo) + if err != nil { + return nil, err + } + json.Unmarshal(iocBytes, &gIOC) + + return gIOC, nil +}