Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report Windows pagefile usage in bytes #1837

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,37 @@ import (
)

var (
modPsapi = windows.NewLazySystemDLL("psapi.dll")
procEnumPageFilesW = modPsapi.NewProc("EnumPageFilesW")
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
modPsapi = windows.NewLazySystemDLL("psapi.dll")

procGetNativeSystemInfo = modKernel32.NewProc("GetNativeSystemInfo")
procEnumPageFilesW = modPsapi.NewProc("EnumPageFilesW")
)

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 getPageSize() uint64 {
var sysInfo systemInfo
procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&sysInfo)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check returned error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetNativeSystemInfo doesn't return error or sets last error per https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo

return uint64(sysInfo.dwPageSize)
}

type pageFileData struct {
name string
used uint64
total uint64
name string
usedPages uint64
totalPages uint64
Comment on lines +56 to +57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

// system type as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-enum_page_file_information
Expand Down Expand Up @@ -60,9 +83,9 @@ func pEnumPageFileCallbackW(pageFiles *[]*pageFileData, enumPageFileInfo *enumPa
pageFileName := syscall.UTF16ToString((*lpFilenamePtr)[:])

pfData := &pageFileData{
name: pageFileName,
used: enumPageFileInfo.totalInUse,
total: enumPageFileInfo.totalSize,
name: pageFileName,
usedPages: enumPageFileInfo.totalInUse,
totalPages: enumPageFileInfo.totalSize,
}

*pageFiles = append(*pageFiles, pfData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ var swapUsageDescriptor = func() pdata.Metric {
metric.InitEmpty()
metric.SetName("system.swap.usage")
metric.SetDescription("Swap (unix) or pagefile (windows) usage.")
metric.SetUnit("pages")
metric.SetUnit("bytes")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metric.SetDataType(pdata.MetricDataTypeIntSum)
sum := metric.IntSum()
sum.InitEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package swapscraper
import (
"context"
"math"
"sync"
"time"

"go.opentelemetry.io/collector/component/componenterror"
Expand All @@ -39,6 +40,8 @@ type scraper struct {
pageReadsPerSecCounter pdh.PerfCounterScraper
pageWritesPerSecCounter pdh.PerfCounterScraper

pageSize uint64

startTime pdata.TimestampUnixNano
prevPagingScrapeTime time.Time
cumulativePageReads float64
Expand All @@ -48,9 +51,16 @@ type scraper struct {
pageFileStats func() ([]*pageFileData, error)
}

var (
once sync.Once
pageSize uint64
)

// newSwapScraper creates a Swap Scraper
func newSwapScraper(_ context.Context, cfg *Config) *scraper {
return &scraper{config: cfg, pageFileStats: getPageFileStats}
once.Do(func() { pageSize = getPageSize() })

return &scraper{config: cfg, pageSize: pageSize, pageFileStats: getPageFileStats}
}

// Initialize
Expand Down Expand Up @@ -118,20 +128,20 @@ func (s *scraper) scrapeAndAppendSwapUsageMetric(metrics pdata.MetricSlice) erro

idx := metrics.Len()
metrics.Resize(idx + 1)
initializeSwapUsageMetric(metrics.At(idx), now, pageFiles)
s.initializeSwapUsageMetric(metrics.At(idx), now, pageFiles)
return nil
}

func initializeSwapUsageMetric(metric pdata.Metric, now pdata.TimestampUnixNano, pageFiles []*pageFileData) {
func (s *scraper) initializeSwapUsageMetric(metric pdata.Metric, now pdata.TimestampUnixNano, pageFiles []*pageFileData) {
swapUsageDescriptor.CopyTo(metric)

idps := metric.IntSum().DataPoints()
idps.Resize(2 * len(pageFiles))

idx := 0
for _, pageFile := range pageFiles {
initializeSwapUsageDataPoint(idps.At(idx+0), now, pageFile.name, usedLabelValue, int64(pageFile.used))
initializeSwapUsageDataPoint(idps.At(idx+1), now, pageFile.name, freeLabelValue, int64(pageFile.total-pageFile.used))
initializeSwapUsageDataPoint(idps.At(idx+0), now, pageFile.name, usedLabelValue, int64(pageFile.usedPages*s.pageSize))
initializeSwapUsageDataPoint(idps.At(idx+1), now, pageFile.name, freeLabelValue, int64((pageFile.totalPages-pageFile.usedPages)*s.pageSize))
idx += 2
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,32 @@ import (
func TestScrapeMetrics_Errors(t *testing.T) {
type testCase struct {
name string
pageFileError error
pageSize uint64
getPageFileStats func() ([]*pageFileData, error)
pageReadsPerSecCounterReturnValue interface{}
pageWritesPerSecCounterReturnValue interface{}
expectedError string
expectedUsedValue int64
expectedFreeValue int64
}

testPageSize := uint64(4096)
testPageFileData := &pageFileData{usedPages: 100, totalPages: 300}

testCases := []testCase{
{
name: "pageFileError",
pageFileError: errors.New("err1"),
expectedError: "err1",
name: "standard",
pageSize: testPageSize,
getPageFileStats: func() ([]*pageFileData, error) {
return []*pageFileData{testPageFileData}, nil
},
expectedUsedValue: int64(testPageFileData.usedPages * testPageSize),
expectedFreeValue: int64((testPageFileData.totalPages - testPageFileData.usedPages) * testPageSize),
},
{
name: "pageFileError",
getPageFileStats: func() ([]*pageFileData, error) { return nil, errors.New("err1") },
expectedError: "err1",
},
{
name: "readsPerSecCounterError",
Expand All @@ -55,7 +70,7 @@ func TestScrapeMetrics_Errors(t *testing.T) {
},
{
name: "multipleErrors",
pageFileError: errors.New("err1"),
getPageFileStats: func() ([]*pageFileData, error) { return nil, errors.New("err1") },
pageReadsPerSecCounterReturnValue: errors.New("err2"),
expectedError: "[err1; err2]",
},
Expand All @@ -64,8 +79,14 @@ func TestScrapeMetrics_Errors(t *testing.T) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
scraper := newSwapScraper(context.Background(), &Config{})
if test.pageFileError != nil {
scraper.pageFileStats = func() ([]*pageFileData, error) { return nil, test.pageFileError }
if test.getPageFileStats != nil {
scraper.pageFileStats = test.getPageFileStats
}
if test.pageSize > 0 {
scraper.pageSize = test.pageSize
} else {
assert.Greater(t, pageSize, uint64(0))
assert.Zero(t, pageSize%4096) // page size on Windows should always be a multiple of 4KB
}

err := scraper.Initialize(context.Background())
Expand All @@ -75,8 +96,15 @@ func TestScrapeMetrics_Errors(t *testing.T) {
scraper.pageReadsPerSecCounter = pdh.NewMockPerfCounter(test.pageReadsPerSecCounterReturnValue)
scraper.pageWritesPerSecCounter = pdh.NewMockPerfCounter(test.pageWritesPerSecCounterReturnValue)

_, err = scraper.ScrapeMetrics(context.Background())
assert.EqualError(t, err, test.expectedError)
metrics, err := scraper.ScrapeMetrics(context.Background())
if test.expectedError != "" {
assert.EqualError(t, err, test.expectedError)
return
}

swapUsageMetric := metrics.At(0)
assert.Equal(t, test.expectedUsedValue, swapUsageMetric.IntSum().DataPoints().At(0).Value())
assert.Equal(t, test.expectedFreeValue, swapUsageMetric.IntSum().DataPoints().At(1).Value())
})
}
}