github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/core/free/free.go (about) 1 // Copyright 2012-2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !plan9 6 // +build !plan9 7 8 // free reports usage information for physical memory and swap space. 9 // 10 // Synopsis: 11 // 12 // free [-k] [-m] [-g] [-t] [-h] [-json] 13 // 14 // Description: 15 // 16 // Read memory information from /proc/meminfo and display a summary for 17 // physical memory and swap space. The unit options use powers of 1024. 18 // 19 // Options: 20 // 21 // -k: display the values in kibibytes 22 // -m: display the values in mebibytes 23 // -g: display the values in gibibytes 24 // -t: display the values in tebibytes 25 // -h: display the values in human-readable form 26 // -json: use JSON output 27 package main 28 29 import ( 30 "encoding/json" 31 "flag" 32 "fmt" 33 "log" 34 ) 35 36 var ( 37 humanOutput = flag.Bool("h", false, "Human output: show automatically the shortest three-digits unit") 38 inBytes = flag.Bool("b", false, "Express the values in bytes") 39 inKB = flag.Bool("k", false, "Express the values in kibibytes (default)") 40 inMB = flag.Bool("m", false, "Express the values in mebibytes") 41 inGB = flag.Bool("g", false, "Express the values in gibibytes") 42 inTB = flag.Bool("t", false, "Express the values in tebibytes") 43 toJSON = flag.Bool("json", false, "Use JSON for output") 44 ) 45 46 type unit uint 47 48 const ( 49 // B is bytes 50 B unit = 0 51 // KB is kibibytes 52 KB = 10 53 // MB is mebibytes 54 MB = 20 55 // GB is gibibytes 56 GB = 30 57 // TB is tebibytes 58 TB = 40 59 ) 60 61 var units = [...]string{"B", "K", "M", "G", "T"} 62 63 // FreeConfig is a structure used to configure the behaviour of Free() 64 type FreeConfig struct { 65 Unit unit 66 HumanOutput bool 67 ToJSON bool 68 } 69 70 // the following types are used for JSON serialization 71 type mainMemInfo struct { 72 Total uint64 `json:"total"` 73 Used uint64 `json:"used"` 74 Free uint64 `json:"free"` 75 Shared uint64 `json:"shared"` 76 Cached uint64 `json:"cached"` 77 Buffers uint64 `json:"buffers"` 78 Available uint64 `json:"available"` 79 } 80 81 type swapInfo struct { 82 Total uint64 `json:"total"` 83 Used uint64 `json:"used"` 84 Free uint64 `json:"free"` 85 } 86 87 // MemInfo represents the main memory and swap space information in a structured 88 // manner, suitable for JSON encoding. 89 type MemInfo struct { 90 Mem mainMemInfo `json:"mem"` 91 Swap swapInfo `json:"swap"` 92 } 93 94 type meminfomap map[string]uint64 95 96 // missingRequiredFields checks if any of the specified fields are present in 97 // the input map. 98 func missingRequiredFields(m meminfomap, fields []string) bool { 99 for _, f := range fields { 100 if _, ok := m[f]; !ok { 101 log.Printf("Missing field '%v'", f) 102 return true 103 } 104 } 105 return false 106 } 107 108 // humanReadableValue returns a string representing the input value, treated as 109 // a size in bytes, interpreted in a human readable form. E.g. the number 10240 110 // woud return the string "10 kB". Note that the decimal part is truncated, not 111 // rounded, so the values are guaranteed to be "at least X" 112 func humanReadableValue(value uint64) string { 113 v := value 114 // bits to shift. 0 means bytes, 10 means kB, and so on. 40 is the highest 115 // and it means tB 116 var shift uint 117 for { 118 if shift >= uint(len(units)*10) { 119 // 4 means tebibyte, we don't go further 120 break 121 } 122 if v/1024 < 1 { 123 break 124 } 125 v /= 1024 126 shift += 10 127 } 128 var decimal uint64 129 if shift > 0 { 130 // no rounding. Is there a better way to do this? 131 decimal = ((value - (value >> shift << shift)) >> (shift - 10)) * 1000 / 1024 / 100 132 } 133 return fmt.Sprintf("%v.%v%v", 134 value>>shift, 135 decimal, 136 units[shift/10], 137 ) 138 } 139 140 // formatValueByConfig formats a size in bytes in the appropriate unit, 141 // depending on whether FreeConfig specifies a human-readable format or a 142 // specific unit 143 func formatValueByConfig(value uint64, config *FreeConfig) string { 144 if config.HumanOutput { 145 return humanReadableValue(value) 146 } 147 // units and decimal part are not printed when a unit is explicitly specified 148 return fmt.Sprintf("%v", value>>config.Unit) 149 } 150 151 // Free prints physical memory and swap space information. The fields will be 152 // expressed with the specified unit (e.g. KB, MB) 153 func Free(config *FreeConfig) error { 154 m, err := meminfo() 155 if err != nil { 156 log.Fatal(err) 157 } 158 mmi, err := getMainMemInfo(m, config) 159 if err != nil { 160 return err 161 } 162 si, err := getSwapInfo(m, config) 163 if err != nil { 164 return err 165 } 166 mi := MemInfo{Mem: *mmi, Swap: *si} 167 if config.ToJSON { 168 jsonData, err := json.Marshal(mi) 169 if err != nil { 170 return err 171 } 172 fmt.Println(string(jsonData)) 173 } else { 174 fmt.Printf(" total used free shared buff/cache available\n") 175 fmt.Printf("%-7s %11v %11v %11v %11v %11v %11v\n", 176 "Mem:", 177 formatValueByConfig(mmi.Total, config), 178 formatValueByConfig(mmi.Used, config), 179 formatValueByConfig(mmi.Free, config), 180 formatValueByConfig(mmi.Shared, config), 181 formatValueByConfig(mmi.Buffers+mmi.Cached, config), 182 formatValueByConfig(mmi.Available, config), 183 ) 184 fmt.Printf("%-7s %11v %11v %11v\n", 185 "Swap:", 186 formatValueByConfig(si.Total, config), 187 formatValueByConfig(si.Used, config), 188 formatValueByConfig(si.Free, config), 189 ) 190 } 191 return nil 192 } 193 194 // validateUnits checks that only one option of -b, -k, -m, -g, -t or -h has been 195 // specified on the command line 196 func validateUnits() bool { 197 count := 0 198 if *inBytes { 199 count++ 200 } 201 if *inKB { 202 count++ 203 } 204 if *inMB { 205 count++ 206 } 207 if *inGB { 208 count++ 209 } 210 if *inTB { 211 count++ 212 } 213 if *humanOutput { 214 count++ 215 } 216 if count > 1 { 217 return false 218 } 219 return true 220 } 221 222 func main() { 223 flag.Parse() 224 if !validateUnits() { 225 log.Fatal("Options -k, -m, -g, -t and -h are mutually exclusive") 226 } 227 config := FreeConfig{ToJSON: *toJSON} 228 if *humanOutput { 229 config.HumanOutput = true 230 } else { 231 switch { 232 case *inBytes: 233 config.Unit = B 234 case *inKB: 235 config.Unit = KB 236 case *inMB: 237 config.Unit = MB 238 case *inGB: 239 config.Unit = GB 240 case *inTB: 241 config.Unit = TB 242 } 243 } 244 245 if err := Free(&config); err != nil { 246 log.Fatal(err) 247 } 248 }