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  }