github.com/ubuntu/ubuntu-report@v1.7.4-0.20240410144652-96f37d845fac/internal/metrics/cmd.go (about) 1 package metrics 2 3 import ( 4 "bytes" 5 "io" 6 "io/ioutil" 7 "os/exec" 8 "strings" 9 10 "github.com/pkg/errors" 11 log "github.com/sirupsen/logrus" 12 "github.com/ubuntu/ubuntu-report/internal/utils" 13 ) 14 15 func (m Metrics) getGPU() []gpuInfo { 16 var gpus []gpuInfo 17 18 r := runCmd(m.gpuInfoCmd) 19 20 results, err := filterAll(r, `^.* 0300: ([a-zA-Z0-9]+:[a-zA-Z0-9]+)( \(rev .*\))?$`) 21 if err != nil { 22 log.Infof("couldn't get GPU info: "+utils.ErrFormat, err) 23 return nil 24 } 25 26 for _, gpuinfo := range results { 27 i := strings.SplitN(gpuinfo, ":", 2) 28 if len(i) != 2 { 29 log.Infof("GPU info should of form vendor:model, got: %s", gpuinfo) 30 continue 31 } 32 gpus = append(gpus, gpuInfo{Vendor: i[0], Model: i[1]}) 33 } 34 35 return gpus 36 } 37 38 func (m Metrics) getCPU() cpuInfo { 39 c := cpuInfo{} 40 41 r := runCmd(m.cpuInfoCmd) 42 43 for result := range filter(r, `{"field": *"(.*)", *"data": *"(.*)"},`, true) { 44 if result.err != nil { 45 log.Infof("Couldn't get CPU info: "+utils.ErrFormat, result.err) 46 return cpuInfo{} 47 } 48 49 key, v := result.r[0], result.r[1] 50 51 switch strings.TrimSpace(key) { 52 case "CPU op-mode(s):": 53 c.OpMode = v 54 case "CPU(s):": 55 c.CPUs = v 56 case "Thread(s) per core:": 57 c.Threads = v 58 case "Core(s) per socket:": 59 c.Cores = v 60 case "Socket(s):": 61 c.Sockets = v 62 case "Vendor ID:": 63 c.Vendor = v 64 case "CPU family:": 65 c.Family = v 66 case "Model:": 67 c.Model = v 68 case "Stepping:": 69 c.Stepping = v 70 case "Model name:": 71 c.Name = v 72 case "Virtualization:": 73 c.Virtualization = v 74 case "Hypervisor vendor:": 75 c.Hypervisor = v 76 case "Virtualization type:": 77 c.VirtualizationType = v 78 } 79 } 80 81 return c 82 } 83 84 func (m Metrics) getScreens() []screenInfo { 85 var screens []screenInfo 86 87 r := runCmd(m.screenInfoCmd) 88 89 var results []string 90 results, err := filterAll(r, `^(?: +(.*)\*|.* connected .* (\d+mm x \d+mm))`) 91 if err != nil { 92 log.Infof("couldn't get Screen info: "+utils.ErrFormat, err) 93 return nil 94 } 95 96 var lastSize string 97 for _, screeninfo := range results { 98 if strings.Index(screeninfo, "mm") > -1 { 99 lastSize = strings.Replace(screeninfo, " ", "", -1) 100 continue 101 } 102 i := strings.Fields(screeninfo) 103 if len(i) < 2 { 104 log.Infof("screen info should be either a screen physical size (connected) or a a resolution + freq, got: %s", screeninfo) 105 continue 106 } 107 if lastSize == "" { 108 log.Infof("We couldn't get physical info size prior to Resolution and Frequency information.") 109 continue 110 } 111 screens = append(screens, screenInfo{Size: lastSize, Resolution: i[0], Frequency: i[len(i)-1]}) 112 } 113 114 return screens 115 } 116 117 func (m Metrics) getPartitions() []float64 { 118 var sizes []float64 119 120 r := runCmd(m.spaceInfoCmd) 121 122 results, err := filterAll(r, `^/dev/([^\s]+ +[^\s]*).*$`) 123 if err != nil { 124 log.Infof("couldn't get Disk info: "+utils.ErrFormat, err) 125 return nil 126 } 127 128 for _, size := range results { 129 // negative lookahead isn't supported in go, so exclude loop devices manually 130 if strings.HasPrefix(size, "loop") { 131 continue 132 } 133 s := strings.Fields(size) 134 if len(s) != 2 { 135 log.Infof("partition size should be of form 'block device size', got: %s", size) 136 continue 137 } 138 v, err := convKBToGB(s[1]) 139 if err != nil { 140 log.Infof("partition size should be an integer: "+utils.ErrFormat, err) 141 continue 142 } 143 sizes = append(sizes, v) 144 } 145 146 return sizes 147 } 148 149 func (m Metrics) getArch() string { 150 b, err := m.archCmd.CombinedOutput() 151 if err != nil { 152 log.Infof("couldn't get Architecture: "+utils.ErrFormat, err) 153 return "" 154 } 155 156 return strings.TrimSpace(string(b)) 157 } 158 159 func (m Metrics) getHwCap() string { 160 if m.hwCapCmd == nil { 161 // if no data return empty string. This is caused by an 162 // unsupported architecture or older version of glibc 163 return "" 164 } 165 166 rSupported := runCmd(m.hwCapCmd) 167 168 // check if there is any hwcap output 169 bytesSupported, err := ioutil.ReadAll(rSupported) 170 if err != nil { 171 log.Infof("Couldn't get hwcap: "+utils.ErrFormat, err) 172 return "" 173 } 174 175 hwCapBytes := []byte("Subdirectories of glibc-hwcaps") 176 hwCapIndex := bytes.Index(bytesSupported, hwCapBytes) 177 if hwCapIndex < 0 { 178 // no glibc-hwcaps, return empty string 179 return "" 180 } 181 182 // remove the legacy hwcap section, as we don't want to report those 183 legacyBytes := []byte("Legacy HWCAP subdirectories") 184 185 legacyIndex := bytes.Index(bytesSupported, legacyBytes) 186 if legacyIndex >= 0 { 187 bytesSupported = bytesSupported[0:legacyIndex] 188 } 189 190 // convert back to io.Reader for the filter functions 191 newSupported := bytes.NewReader(bytesSupported) 192 193 // now find which version is supported 194 resultSupported, err := filterFirst(newSupported, `^(?:(.*) +.*supported, searched.*)`, false) 195 if err != nil { 196 log.Infof("No supported hwcap: "+utils.ErrFormat, err) 197 return "-" 198 } 199 200 return resultSupported 201 } 202 203 func runCmd(cmd *exec.Cmd) io.Reader { 204 pr, pw := io.Pipe() 205 cmd.Stdout = pw 206 207 go func() { 208 err := cmd.Run() 209 if err != nil { 210 pw.CloseWithError(errors.Wrapf(err, "'%s' return an error", cmd.Args)) 211 return 212 } 213 pw.Close() 214 }() 215 return pr 216 }