307 lines
10 KiB
Go
307 lines
10 KiB
Go
|
//go:build linux
|
|||
|
|
|||
|
package hcache
|
|||
|
|
|||
|
/*
|
|||
|
* Copyright 2015 Albert P. Tobey <atobey@datastax.com> @AlTobey
|
|||
|
*
|
|||
|
* 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.
|
|||
|
*
|
|||
|
* pcstat.go - page cache stat
|
|||
|
*
|
|||
|
* uses the mincore(2) syscall to find out which pages (almost always 4k)
|
|||
|
* of a file are currently cached in memory
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
import (
|
|||
|
"encoding/json"
|
|||
|
"fmt"
|
|||
|
pcstat "github.com/tobert/pcstat/pkg"
|
|||
|
"log"
|
|||
|
"os"
|
|||
|
"strings"
|
|||
|
)
|
|||
|
|
|||
|
type PcStatusList []pcstat.PcStatus
|
|||
|
|
|||
|
func (a PcStatusList) Len() int {
|
|||
|
return len(a)
|
|||
|
}
|
|||
|
func (a PcStatusList) Swap(i, j int) {
|
|||
|
a[i], a[j] = a[j], a[i]
|
|||
|
}
|
|||
|
func (a PcStatusList) Less(i, j int) bool {
|
|||
|
return a[j].Cached < a[i].Cached
|
|||
|
}
|
|||
|
|
|||
|
func (stats PcStatusList) FormatUnicode() {
|
|||
|
maxName := stats.maxNameLen()
|
|||
|
|
|||
|
// create horizontal grid line
|
|||
|
pad := strings.Repeat("─", maxName+2)
|
|||
|
top := fmt.Sprintf("┌%s┬────────────────┬─────────────┬────────────────┬─────────────┬─────────┐", pad)
|
|||
|
hr := fmt.Sprintf("├%s┼────────────────┼─────────────┼────────────────┼─────────────┼─────────┤", pad)
|
|||
|
bot := fmt.Sprintf("└%s┴────────────────┴─────────────┴────────────────┴─────────────┴─────────┘", pad)
|
|||
|
|
|||
|
var size_sum, page_sum, cached_page_sum, cached_size, cached_size_sum int64
|
|||
|
|
|||
|
fmt.Println(top)
|
|||
|
|
|||
|
// -nohdr may be chosen to save 2 lines of precious vertical space
|
|||
|
if !nohdrFlag {
|
|||
|
pad = strings.Repeat(" ", maxName-4)
|
|||
|
fmt.Printf("│ Name%s │ Size │ Pages │ Cached Size │ Cached Pages│ Percent │\n", pad)
|
|||
|
fmt.Println(hr)
|
|||
|
}
|
|||
|
|
|||
|
for _, pcs := range stats {
|
|||
|
pad = strings.Repeat(" ", maxName-len(pcs.Name))
|
|||
|
|
|||
|
// The cache is counted through the page,can't count accurately.
|
|||
|
// Here cached file size is calculated by pcs.Size and pcs.Percent,
|
|||
|
// so it is not completely accurate, but it has reference value.
|
|||
|
cached_size = int64(float64(pcs.Size) * pcs.Percent / 100)
|
|||
|
|
|||
|
// %-7.3f was chosen to make it easy to scan the percentages vertically
|
|||
|
// I tried a few different formats only this one kept the decimals aligned
|
|||
|
fmt.Printf("│ %s%s │ %-15s│ %-12d│ %-15s│ %-12d│ %-7.3f │\n",
|
|||
|
pcs.Name, pad, ConvertUnit(pcs.Size), pcs.Pages, ConvertUnit(cached_size), pcs.Cached, pcs.Percent)
|
|||
|
|
|||
|
size_sum += pcs.Size
|
|||
|
page_sum += int64(pcs.Pages)
|
|||
|
cached_page_sum += int64(pcs.Cached)
|
|||
|
cached_size_sum += cached_size
|
|||
|
}
|
|||
|
|
|||
|
fmt.Println(hr)
|
|||
|
pad = strings.Repeat(" ", maxName-len("Sum"))
|
|||
|
fmt.Printf("│ %s%s │ %-15s│ %-12d│ %-15s│ %-12d│ %-7.3f │\n",
|
|||
|
"Sum", pad, ConvertUnit(size_sum), page_sum, ConvertUnit(cached_size_sum), cached_page_sum, (float64(cached_page_sum)/float64(page_sum))*100.00)
|
|||
|
fmt.Println(bot)
|
|||
|
}
|
|||
|
|
|||
|
func (stats PcStatusList) FormatText() {
|
|||
|
maxName := stats.maxNameLen()
|
|||
|
|
|||
|
// create horizontal grid line
|
|||
|
pad := strings.Repeat("-", maxName+2)
|
|||
|
top := fmt.Sprintf("+%s+----------------+-------------+----------------+-------------+---------+", pad)
|
|||
|
hr := fmt.Sprintf("|%s+----------------+-------------+----------------+-------------+---------|", pad)
|
|||
|
bot := fmt.Sprintf("+%s+----------------+-------------+----------------+-------------+---------+", pad)
|
|||
|
var size_sum, page_sum, cached_page_sum, cached_size, cached_size_sum int64
|
|||
|
|
|||
|
fmt.Println(top)
|
|||
|
|
|||
|
// -nohdr may be chosen to save 2 lines of precious vertical space
|
|||
|
if !nohdrFlag {
|
|||
|
pad = strings.Repeat(" ", maxName-4)
|
|||
|
fmt.Printf("| Name%s | Size │ Pages │ Cached Size │ Cached Pages│ Percent │\n", pad)
|
|||
|
fmt.Println(hr)
|
|||
|
}
|
|||
|
|
|||
|
for _, pcs := range stats {
|
|||
|
pad = strings.Repeat(" ", maxName-len(pcs.Name))
|
|||
|
cached_size = int64(float64(pcs.Size) * pcs.Percent / 100)
|
|||
|
|
|||
|
// %-7.3f was chosen to make it easy to scan the percentages vertically
|
|||
|
// I tried a few different formats only this one kept the decimals aligned
|
|||
|
fmt.Printf("| %s%s | %-15s| %-12d| %-15s| %-12d| %-7.3f |\n",
|
|||
|
pcs.Name, pad, ConvertUnit(pcs.Size), pcs.Pages, ConvertUnit(cached_size), pcs.Cached, pcs.Percent)
|
|||
|
|
|||
|
size_sum += pcs.Size
|
|||
|
page_sum += int64(pcs.Pages)
|
|||
|
cached_page_sum += int64(pcs.Cached)
|
|||
|
cached_size_sum += cached_size
|
|||
|
}
|
|||
|
|
|||
|
fmt.Println(hr)
|
|||
|
pad = strings.Repeat(" ", maxName-len("Sum"))
|
|||
|
fmt.Printf("│ %s%s │ %-15s│ %-12d│ %-15s│ %-12d│ %-7.3f │\n",
|
|||
|
"Sum", pad, ConvertUnit(size_sum), page_sum, ConvertUnit(cached_size_sum), cached_page_sum, (float64(cached_page_sum)/float64(page_sum))*100.00)
|
|||
|
fmt.Println(bot)
|
|||
|
}
|
|||
|
|
|||
|
func (stats PcStatusList) FormatPlain() {
|
|||
|
maxName := stats.maxNameLen()
|
|||
|
|
|||
|
var size_sum, page_sum, cached_page_sum, cached_size, cached_size_sum int64
|
|||
|
|
|||
|
// -nohdr may be chosen to save 2 lines of precious vertical space
|
|||
|
if !nohdrFlag {
|
|||
|
pad := strings.Repeat(" ", maxName-4)
|
|||
|
fmt.Printf("Name%s Size Pages Cached Size Cached Pages Percent\n", pad)
|
|||
|
}
|
|||
|
|
|||
|
for _, pcs := range stats {
|
|||
|
pad := strings.Repeat(" ", maxName-len(pcs.Name))
|
|||
|
cached_size = int64(float64(pcs.Size) * pcs.Percent / 100)
|
|||
|
|
|||
|
// %-7.3f was chosen to make it easy to scan the percentages vertically
|
|||
|
// I tried a few different formats only this one kept the decimals aligned
|
|||
|
fmt.Printf("%s%s %-15s %-12d %-15s %-12d %-7.3f\n",
|
|||
|
pcs.Name, pad, ConvertUnit(pcs.Size), pcs.Pages, ConvertUnit(cached_size), pcs.Cached, pcs.Percent)
|
|||
|
|
|||
|
size_sum += pcs.Size
|
|||
|
page_sum += int64(pcs.Pages)
|
|||
|
cached_page_sum += int64(pcs.Cached)
|
|||
|
cached_size_sum += cached_size
|
|||
|
}
|
|||
|
|
|||
|
pad := strings.Repeat(" ", maxName-len("Sum"))
|
|||
|
fmt.Printf("%s%s %-15s %-12d %-15s %-12d %-7.3f\n",
|
|||
|
"Sum", pad, ConvertUnit(size_sum), page_sum, ConvertUnit(cached_size_sum), cached_page_sum, (float64(cached_page_sum)/float64(page_sum))*100.00)
|
|||
|
}
|
|||
|
|
|||
|
func (stats PcStatusList) FormatTerse() {
|
|||
|
|
|||
|
if !nohdrFlag {
|
|||
|
fmt.Println("name,size,timestamp,mtime,pages,cached,percent")
|
|||
|
}
|
|||
|
for _, pcs := range stats {
|
|||
|
time := pcs.Timestamp.Unix()
|
|||
|
mtime := pcs.Mtime.Unix()
|
|||
|
fmt.Printf("%s,%d,%d,%d,%d,%d,%g\n",
|
|||
|
pcs.Name, pcs.Size, time, mtime, pcs.Pages, pcs.Cached, pcs.Percent)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func (stats PcStatusList) FormatJson(clearpps bool) {
|
|||
|
// clear the per-page status when requested
|
|||
|
// emits an empty "status": [] field in the JSON when disabled, but NBD.
|
|||
|
if clearpps {
|
|||
|
for i := range stats {
|
|||
|
stats[i].PPStat = nil
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
b, err := json.Marshal(stats)
|
|||
|
if err != nil {
|
|||
|
log.Fatalf("JSON formatting failed: %s\n", err)
|
|||
|
}
|
|||
|
os.Stdout.Write(b)
|
|||
|
fmt.Println("")
|
|||
|
}
|
|||
|
|
|||
|
// references:
|
|||
|
// http://www.unicode.org/charts/PDF/U2580.pdf
|
|||
|
// https://github.com/puppetlabs/mcollective-puppet-agent/blob/master/application/puppet.rb#L143
|
|||
|
// https://github.com/holman/spark
|
|||
|
func (stats PcStatusList) FormatHistogram() {
|
|||
|
ws := getwinsize()
|
|||
|
maxName := stats.maxNameLen()
|
|||
|
|
|||
|
// block elements are wider than characters, so only use 1/2 the available columns
|
|||
|
buckets := (int(ws.ws_col)-maxName)/2 - 10
|
|||
|
|
|||
|
for _, pcs := range stats {
|
|||
|
pad := strings.Repeat(" ", maxName-len(pcs.Name))
|
|||
|
fmt.Printf("%s%s % 8d ", pcs.Name, pad, pcs.Pages)
|
|||
|
|
|||
|
// when there is enough room display on/off for every page
|
|||
|
if buckets > pcs.Pages {
|
|||
|
for _, v := range pcs.PPStat {
|
|||
|
if v {
|
|||
|
fmt.Print("\u2588") // full block = 100%
|
|||
|
} else {
|
|||
|
fmt.Print("\u2581") // lower 1/8 block
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
bsz := pcs.Pages / buckets
|
|||
|
fbsz := float64(bsz)
|
|||
|
total := 0.0
|
|||
|
for i, v := range pcs.PPStat {
|
|||
|
if v {
|
|||
|
total++
|
|||
|
}
|
|||
|
|
|||
|
if (i+1)%bsz == 0 {
|
|||
|
avg := total / fbsz
|
|||
|
if total == 0 {
|
|||
|
fmt.Print("\u2581") // lower 1/8 block = 0
|
|||
|
} else if avg < 0.16 {
|
|||
|
fmt.Print("\u2582") // lower 2/8 block
|
|||
|
} else if avg < 0.33 {
|
|||
|
fmt.Print("\u2583") // lower 3/8 block
|
|||
|
} else if avg < 0.50 {
|
|||
|
fmt.Print("\u2584") // lower 4/8 block
|
|||
|
} else if avg < 0.66 {
|
|||
|
fmt.Print("\u2585") // lower 5/8 block
|
|||
|
} else if avg < 0.83 {
|
|||
|
fmt.Print("\u2586") // lower 6/8 block
|
|||
|
} else if avg < 1.00 {
|
|||
|
fmt.Print("\u2587") // lower 7/8 block
|
|||
|
} else {
|
|||
|
fmt.Print("\u2588") // full block = 100%
|
|||
|
}
|
|||
|
|
|||
|
total = 0
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
fmt.Println("")
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
|
|||
|
// convert long paths to their basename with the -bname flag
|
|||
|
// this overwrites the original filename in pcs but it doesn't matter since
|
|||
|
// it's not used to access the file again -- and should not be!
|
|||
|
if bnameFlag {
|
|||
|
pcs.Name = path.Base(fname)
|
|||
|
}
|
|||
|
*/
|
|||
|
|
|||
|
// maxNameLen returns the len of longest filename in the stat list
|
|||
|
// if the bnameFlag is set, this will return the max basename len
|
|||
|
func (stats PcStatusList) maxNameLen() int {
|
|||
|
var maxName int
|
|||
|
for _, pcs := range stats {
|
|||
|
if len(pcs.Name) > maxName {
|
|||
|
maxName = len(pcs.Name)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if maxName < 5 {
|
|||
|
maxName = 5
|
|||
|
}
|
|||
|
return maxName
|
|||
|
}
|
|||
|
|
|||
|
// define some const unit
|
|||
|
// convert origin size data to a friendly readable string.
|
|||
|
func ConvertUnit(byteSize int64) string {
|
|||
|
const KB int64 = 1024
|
|||
|
const MB int64 = 1024 * KB
|
|||
|
const GB int64 = 1024 * MB
|
|||
|
const TB int64 = 1024 * GB
|
|||
|
const PB int64 = 1024 * TB
|
|||
|
|
|||
|
switch {
|
|||
|
case byteSize >= PB:
|
|||
|
return fmt.Sprintf("%.3fP", (float64(byteSize) / float64(PB)))
|
|||
|
case byteSize >= TB:
|
|||
|
return fmt.Sprintf("%.3fT", (float64(byteSize) / float64(TB)))
|
|||
|
case byteSize >= GB:
|
|||
|
return fmt.Sprintf("%.3fG", (float64(byteSize) / float64(GB)))
|
|||
|
case byteSize >= MB:
|
|||
|
return fmt.Sprintf("%.3fM", (float64(byteSize) / float64(MB)))
|
|||
|
case byteSize >= KB:
|
|||
|
return fmt.Sprintf("%.3fK", (float64(byteSize) / float64(KB)))
|
|||
|
default:
|
|||
|
return fmt.Sprintf("%dB", byteSize)
|
|||
|
}
|
|||
|
}
|