github.com/ubuntu/ubuntu-report@v1.7.4-0.20240410144652-96f37d845fac/internal/metrics/files.go (about)

     1  package metrics
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/ioutil"
     6  	"math"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/pkg/errors"
    13  	log "github.com/sirupsen/logrus"
    14  	"github.com/ubuntu/ubuntu-report/internal/utils"
    15  )
    16  
    17  func (m Metrics) getVersion() string {
    18  	v, err := matchFromFile(filepath.Join(m.root, "etc/os-release"), `^VERSION_ID="(.*)"$`, false)
    19  	if err != nil {
    20  		log.Infof("couldn't get version information from os-release: "+utils.ErrFormat, err)
    21  		return ""
    22  	}
    23  	return v
    24  }
    25  
    26  func (m Metrics) getRAM() *float64 {
    27  	s, err := matchFromFile(filepath.Join(m.root, "proc/meminfo"), `^MemTotal: +(\d+) kB$`, false)
    28  	if err != nil {
    29  		log.Infof("couldn't get RAM information from meminfo: "+utils.ErrFormat, err)
    30  		return nil
    31  	}
    32  	v, err := convKBToGB(s)
    33  	if err != nil {
    34  		log.Infof("partition size should be an integer: "+utils.ErrFormat, err)
    35  		return nil
    36  	}
    37  	return &v
    38  }
    39  
    40  func (m Metrics) getTimeZone() string {
    41  	v, err := getFromFileTrimmed(filepath.Join(m.root, "etc/timezone"))
    42  	if err != nil {
    43  		log.Infof("couldn't get timezone information: "+utils.ErrFormat, err)
    44  		return ""
    45  	}
    46  	if strings.Contains(v, "\n") {
    47  		log.Infof(utils.ErrFormat, errors.Errorf("malformed timezone information, file contains: %s", v))
    48  		return ""
    49  	}
    50  	return v
    51  }
    52  
    53  func (m Metrics) getAutologin() bool {
    54  	v, err := matchFromFile(filepath.Join(m.root, "etc/gdm3/custom.conf"), `^AutomaticLoginEnable ?= ?(.*)$`, true)
    55  	if err != nil {
    56  		log.Infof("couldn't get autologin information from gdm: "+utils.ErrFormat, err)
    57  		return false
    58  	}
    59  	if strings.ToLower(v) != "true" {
    60  		return false
    61  	}
    62  	return true
    63  }
    64  
    65  func (m Metrics) getOEM() (string, string, string, string) {
    66  	v, err := getFromFileTrimmed(filepath.Join(m.root, "sys/class/dmi/id/sys_vendor"))
    67  	if err != nil {
    68  		log.Infof("couldn't get sys vendor information: "+utils.ErrFormat, err)
    69  	}
    70  	if strings.Contains(v, "\n") {
    71  		log.Infof(utils.ErrFormat, errors.Errorf("malformed sys vendor information, file contains: %s", v))
    72  		v = ""
    73  	}
    74  	p, err := getFromFileTrimmed(filepath.Join(m.root, "sys/class/dmi/id/product_name"))
    75  	if err != nil {
    76  		log.Infof("couldn't get sys product name information: "+utils.ErrFormat, err)
    77  	}
    78  	if strings.Contains(p, "\n") {
    79  		log.Infof(utils.ErrFormat, errors.Errorf("malformed sys product name information, file contains: %s", p))
    80  		p = ""
    81  	}
    82  	f, err := getFromFileTrimmed(filepath.Join(m.root, "sys/class/dmi/id/product_family"))
    83  	if err != nil {
    84  		log.Infof("couldn't get sys product family information: "+utils.ErrFormat, err)
    85  	}
    86  	if strings.Contains(f, "\n") {
    87  		log.Infof(utils.ErrFormat, errors.Errorf("malformed sys product family information, file contains: %s", f))
    88  		f = ""
    89  	}
    90  	dcd, err := matchFromFile(filepath.Join(m.root, "var/lib/ubuntu_dist_channel"), `^([^\s#]+)$`, true)
    91  	if err != nil {
    92  		log.Infof("no DCD information: "+utils.ErrFormat, err)
    93  	}
    94  	return v, p, f, dcd
    95  }
    96  
    97  func (m Metrics) getBIOS() (string, string) {
    98  	vd, err := getFromFileTrimmed(filepath.Join(m.root, "sys/class/dmi/id/bios_vendor"))
    99  	if err != nil {
   100  		log.Infof("couldn't get bios vendor information: "+utils.ErrFormat, err)
   101  		vd = ""
   102  	}
   103  	if strings.Contains(vd, "\n") {
   104  		log.Infof(utils.ErrFormat, errors.Errorf("malformed bios vendor information, file contains: %s", vd))
   105  		vd = ""
   106  	}
   107  	ve, err := getFromFileTrimmed(filepath.Join(m.root, "sys/class/dmi/id/bios_version"))
   108  	if err != nil {
   109  		log.Infof("couldn't get bios version: "+utils.ErrFormat, err)
   110  		ve = ""
   111  	}
   112  	if strings.Contains(ve, "\n") {
   113  		log.Infof(utils.ErrFormat, errors.Errorf("malformed bios version information, file contains: %s", ve))
   114  		ve = ""
   115  	}
   116  	return vd, ve
   117  }
   118  
   119  func (m Metrics) getLivePatch() bool {
   120  	if _, err := os.Stat(filepath.Join(m.root, "var/snap/canonical-livepatch/common/machine-token")); err != nil {
   121  		return false
   122  	}
   123  	return true
   124  }
   125  
   126  func (m Metrics) getDisks() []float64 {
   127  	var sizes []float64
   128  
   129  	blockFolder := filepath.Join(m.root, "sys/block")
   130  	dirs, err := ioutil.ReadDir(blockFolder)
   131  	if err != nil {
   132  		log.Infof("couldn't get disk block information: "+utils.ErrFormat, err)
   133  		return nil
   134  	}
   135  
   136  	for _, d := range dirs {
   137  		if !(strings.HasPrefix(d.Name(), "hd") || strings.HasPrefix(d.Name(), "sd") || strings.HasPrefix(d.Name(), "vd")) {
   138  			continue
   139  		}
   140  
   141  		v, err := getFromFileTrimmed(filepath.Join(blockFolder, d.Name(), "size"))
   142  		if err != nil {
   143  			log.Infof("couldn't get disk block information for %s: "+utils.ErrFormat, d.Name(), err)
   144  			continue
   145  		}
   146  		s, err := strconv.Atoi(v)
   147  		if err != nil {
   148  			log.Infof("number of block for disk %s isn't an integer: "+utils.ErrFormat, d.Name(), err)
   149  			continue
   150  		}
   151  
   152  		v, err = getFromFileTrimmed(filepath.Join(blockFolder, d.Name(), "queue/logical_block_size"))
   153  		if err != nil {
   154  			log.Infof("couldn't get disk block information for %s: "+utils.ErrFormat, d.Name(), err)
   155  			continue
   156  		}
   157  		bs, err := strconv.Atoi(v)
   158  		if err != nil {
   159  			log.Infof("block size for disk %s isn't an integer: "+utils.ErrFormat, d.Name(), err)
   160  			continue
   161  		}
   162  
   163  		// convert in Gib in .1 precision
   164  		size := float64(s) * float64(bs) / (1000 * 1000 * 1000)
   165  		size = math.Round(size*10) / 10
   166  
   167  		sizes = append(sizes, size)
   168  	}
   169  
   170  	return sizes
   171  }
   172  
   173  func (m Metrics) installerInfo() json.RawMessage {
   174  	return getAndValidateJSONFromFile(filepath.Join(m.root, installerLogsPath), "install")
   175  }
   176  
   177  func (m Metrics) upgradeInfo() json.RawMessage {
   178  	return getAndValidateJSONFromFile(filepath.Join(m.root, upgradeLogsPath), "upgrade")
   179  }
   180  
   181  func matchFromFile(p, regex string, notFoundOk bool) (string, error) {
   182  	f, err := os.Open(p)
   183  	if err != nil {
   184  		return "", errors.Wrapf(err, "couldn't open %s", p)
   185  	}
   186  	defer f.Close()
   187  
   188  	return filterFirst(f, regex, notFoundOk)
   189  }
   190  
   191  func getFromFile(p string) ([]byte, error) {
   192  	f, err := os.Open(p)
   193  	if err != nil {
   194  		return nil, errors.Wrapf(err, "couldn't open %s", p)
   195  	}
   196  	defer f.Close()
   197  
   198  	b, err := ioutil.ReadAll(f)
   199  	if err != nil {
   200  		return nil, errors.Wrapf(err, "couldn't read %s", p)
   201  	}
   202  
   203  	return b, nil
   204  }
   205  
   206  func getFromFileTrimmed(p string) (string, error) {
   207  	b, err := getFromFile(p)
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  	return strings.TrimSpace(string(b)), nil
   212  }
   213  
   214  func getAndValidateJSONFromFile(p string, errmsg string) json.RawMessage {
   215  	b, err := getFromFile(p)
   216  	if err != nil {
   217  		log.Infof("no %s data found: "+utils.ErrFormat, errmsg, err)
   218  		return nil
   219  	}
   220  	if !json.Valid(b) {
   221  		log.Infof("%s data found, but not valid json.", errmsg)
   222  		return nil
   223  	}
   224  	return json.RawMessage(b)
   225  }