//go:build linux package hcache /* * Copyright 2015 Albert P. Tobey @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) } }