Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions metric/memory/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 memory

import (
"fmt"

"github.com/elastic/elastic-agent-libs/opt"
"github.com/elastic/elastic-agent-system-metrics/metric"
"github.com/elastic/elastic-agent-system-metrics/metric/system/resolve"
)

// Memory holds os-specifc memory usage data
// The vast majority of these values are cross-platform
// However, we're wrapping all them for the sake of safety, and for the more variable swap metrics
type Memory struct {
Total opt.Uint `struct:"total,omitempty"`
Used UsedMemStats `struct:"used,omitempty"`

Free opt.Uint `struct:"free,omitempty"`
Cached opt.Uint `struct:"cached,omitempty"`
// "Actual" values are, technically, a linux-only concept
// For better or worse we've expanded it to include "derived"
// Memory values on other platforms, which we should
// probably keep for the sake of backwards compatibility
// However, because the derived value varies from platform to platform,
// We may want to more precisely document what these mean.
Actual ActualMemoryMetrics `struct:"actual,omitempty"`

// Swap metrics
Swap SwapMetrics `struct:"swap,omitempty"`
}

// UsedMemStats wraps used.* memory metrics
type UsedMemStats struct {
Pct opt.Float `struct:"pct,omitempty"`
Bytes opt.Uint `struct:"bytes,omitempty"`
}

// ActualMemoryMetrics wraps the actual.* memory metrics
type ActualMemoryMetrics struct {
Free opt.Uint `struct:"free,omitempty"`
Used UsedMemStats `struct:"used,omitempty"`
}

// SwapMetrics wraps swap.* memory metrics
type SwapMetrics struct {
Total opt.Uint `struct:"total,omitempty"`
Used UsedMemStats `struct:"used,omitempty"`
Free opt.Uint `struct:"free,omitempty"`
}

// Get returns platform-independent memory metrics.
func Get(procfs resolve.Resolver) (Memory, error) {
base, err := get(procfs)
if err != nil {
return Memory{}, fmt.Errorf("error getting system memory info: %w", err)
}
base.fillPercentages()
return base, nil
}

// IsZero implements the zeroer interface for structform's folders
func (used UsedMemStats) IsZero() bool {
return used.Pct.IsZero() && used.Bytes.IsZero()
}

// IsZero implements the zeroer interface for structform's folders
func (swap SwapMetrics) IsZero() bool {
return swap.Free.IsZero() && swap.Used.IsZero() && swap.Total.IsZero()
}

func (base *Memory) fillPercentages() {
// Add percentages
// In theory, `Used` and `Total` are available everywhere, so assume values are good.
if base.Total.Exists() && base.Total.ValueOr(0) != 0 {
percUsed := float64(base.Used.Bytes.ValueOr(0)) / float64(base.Total.ValueOr(1))
base.Used.Pct = opt.FloatWith(metric.Round(percUsed))

actualPercUsed := float64(base.Actual.Used.Bytes.ValueOr(0)) / float64(base.Total.ValueOr(0))
base.Actual.Used.Pct = opt.FloatWith(metric.Round(actualPercUsed))
}

if base.Swap.Total.ValueOr(0) != 0 && base.Swap.Used.Bytes.Exists() {
perc := float64(base.Swap.Used.Bytes.ValueOr(0)) / float64(base.Swap.Total.ValueOr(0))
base.Swap.Used.Pct = opt.FloatWith(metric.Round(perc))
}
}
76 changes: 76 additions & 0 deletions metric/memory/memory_aix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 memory

/*
#cgo LDFLAGS: -L/usr/lib -lperfstat

#include <libperfstat.h>
#include <procinfo.h>
#include <unistd.h>
#include <utmp.h>
#include <sys/mntctl.h>
#include <sys/proc.h>
#include <sys/types.h>
#include <sys/vmount.h>

*/
import "C"

import (
"fmt"
"os"

"github.com/elastic/elastic-agent-libs/opt"
"github.com/elastic/elastic-agent-system-metrics/metric/system/resolve"
)

var system struct {
ticks uint64
btime uint64
pagesize uint64
}

func init() {
// sysconf(_SC_CLK_TCK) returns the number of ticks by second.
system.ticks = uint64(C.sysconf(C._SC_CLK_TCK))
system.pagesize = uint64(os.Getpagesize())
}

func get(_ resolve.Resolver) (Memory, error) {
memData := Memory{}
meminfo := C.perfstat_memory_total_t{}
_, err := C.perfstat_memory_total(nil, &meminfo, C.sizeof_perfstat_memory_total_t, 1)
if err != nil {
return memData, fmt.Errorf("perfstat_memory_total: %s", err)
}

totalMem := uint64(meminfo.real_total) * system.pagesize
freeMem := uint64(meminfo.real_free) * system.pagesize

memData.Total = opt.UintWith(totalMem)
memData.Free = opt.UintWith(freeMem)

kern := uint64(meminfo.numperm) * system.pagesize // number of pages in file cache

memData.Used.Bytes = opt.UintWith(totalMem - freeMem)
memData.Actual.Free = opt.UintWith(freeMem + kern)
memData.Actual.Used.Bytes = opt.UintWith(memData.Used.Bytes.ValueOr(0) - kern)

return memData, nil
}
132 changes: 132 additions & 0 deletions metric/memory/memory_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 memory

/*
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/mount.h>
#include <mach/mach_init.h>
#include <mach/mach_host.h>
#include <mach/host_info.h>
#include <libproc.h>
#include <mach/processor_info.h>
#include <mach/vm_map.h>
*/
import "C"

import (
"bytes"
"encoding/binary"
"fmt"
"syscall"
"unsafe"

"github.com/elastic/elastic-agent-libs/opt"
"github.com/elastic/elastic-agent-system-metrics/metric/system/resolve"
)

type xswUsage struct {
Total, Avail, Used uint64
}

// get is the darwin implementation for fetching Memory data
func get(_ resolve.Resolver) (Memory, error) {
var vmstat C.vm_statistics_data_t

mem := Memory{}

var total uint64

if err := sysctlbyname("hw.memsize", &total); err != nil {
return Memory{}, fmt.Errorf("error getting memsize: %w", err)
}
mem.Total = opt.UintWith(total)

if err := vmInfo(&vmstat); err != nil {
return Memory{}, fmt.Errorf("error getting VM info")
}

kern := uint64(vmstat.inactive_count) << 12
free := uint64(vmstat.free_count) << 12

mem.Free = opt.UintWith(free)
mem.Used.Bytes = opt.UintWith(total - free)

mem.Actual.Free = opt.UintWith(free + kern)
mem.Actual.Used.Bytes = opt.UintWith((total - free) - kern)

var err error
mem.Swap, err = getSwap()
if err != nil {
return mem, fmt.Errorf("error getting swap memory: %w", err)
}

return mem, nil
}

// Get fetches swap data
func getSwap() (SwapMetrics, error) {
swUsage := xswUsage{}

swap := SwapMetrics{}
if err := sysctlbyname("vm.swapusage", &swUsage); err != nil {
return swap, fmt.Errorf("error getting swap usage: %w", err)
}

swap.Total = opt.UintWith(swUsage.Total)
swap.Used.Bytes = opt.UintWith(swUsage.Used)
swap.Free = opt.UintWith(swUsage.Avail)

return swap, nil
}

// generic Sysctl buffer unmarshalling
func sysctlbyname(name string, data interface{}) (err error) {
val, err := syscall.Sysctl(name)
if err != nil {
return err
}

buf := []byte(val)

switch v := data.(type) {
case *uint64:
*v = *(*uint64)(unsafe.Pointer(&buf[0]))
return

Check failure on line 111 in metric/memory/memory_darwin.go

View workflow job for this annotation

GitHub Actions / lint-darwin

naked return in func `sysctlbyname` with 16 lines of code (nakedret)
}

bbuf := bytes.NewBuffer([]byte(val))
return binary.Read(bbuf, binary.LittleEndian, data)
}

func vmInfo(vmstat *C.vm_statistics_data_t) error {
var count C.mach_msg_type_number_t = C.HOST_VM_INFO_COUNT

status := C.host_statistics(
C.host_t(C.mach_host_self()),

Check failure on line 122 in metric/memory/memory_darwin.go

View workflow job for this annotation

GitHub Actions / lint-darwin

unnecessary conversion (unconvert)
C.HOST_VM_INFO,
C.host_info_t(unsafe.Pointer(vmstat)),
&count)

if status != C.KERN_SUCCESS {
return fmt.Errorf("host_statistics=%d", status)
}

return nil
}
Loading
Loading