Skip to content

Commit

Permalink
refactor: extract types and parsing into subpackages (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
elmeyer authored Dec 13, 2023
1 parent 52c5f20 commit 0d44b4a
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 214 deletions.
74 changes: 10 additions & 64 deletions cmd/lsirq/lsirq.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,26 @@
package main

import (
"bufio"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"regexp"
"sort"
"strconv"
"strings"

"github.com/TimRots/gutil-linux/irq"
)

const (
PATH_PROC_INTERRUPTS = "/proc/interrupts"
description = "Utility to display kernel interrupt information."
usage = "Usage: lsirq [OPTIONS]\n\n%s\n\nOptions:\n"
description = "Utility to display kernel interrupt information."
usage = "Usage: lsirq [OPTIONS]\n\n%s\n\nOptions:\n"
)

var (
active_cpu_count int
Interrupts []Interrupt
irq string
name []string
noheadings = flag.Bool("noheadings", false, "don't print headings")
pairs = flag.Bool("pairs", false, "use key=\"value\" output format")
jsonoutput = flag.Bool("json", false, "use JSON output format")
noheadings = flag.Bool("noheadings", false, "don't print headings")
pairs = flag.Bool("pairs", false, "use key=\"value\" output format")
jsonoutput = flag.Bool("json", false, "use JSON output format")
)

type Interrupt struct {
Irq string
Total int
Name string
}

func init() {
flag.BoolVar(noheadings, "n", false, "")
flag.BoolVar(pairs, "p", false, "")
Expand All @@ -48,52 +34,12 @@ func init() {
}
}

func irqStat() []Interrupt {
f, err := os.Open(PATH_PROC_INTERRUPTS)
func PrintInterrupts(option string, ops ...bool) {
irqStat, err := irq.IrqStat()
if err != nil {
log.Fatal(err)
}
defer f.Close()

scanner := bufio.NewScanner(f)
for ln := 0; scanner.Scan(); ln++ {
if ln == 0 {
// First line shows the active cpus
active_cpu_count = strings.Count(scanner.Text(), "CPU")
continue
}

// Match all non-whitespace character sequences
i, total, words := 0, 0, regexp.MustCompile(`[\S]+`).FindAllString(scanner.Text(), -1)
for _, word := range words {
// As the lines are variadic in length we use active_cpu_count and
// i count to determine how to parse the value based on the word its position.
i++
switch {
case i == 1:
irq = strings.ReplaceAll(string(word), ":", "")
case i <= active_cpu_count+1:
numint, _ := strconv.Atoi(word)
total += numint
case i > active_cpu_count+1:
name = append(name, string(word))
}
}
Interrupts = append(Interrupts, Interrupt{irq, total, strings.Join(name, " ")})
irq, name = "", nil

}

// Sort slice by number
sort.Slice(Interrupts, func(y, z int) bool {
return Interrupts[y].Total > Interrupts[z].Total
})

return Interrupts
}

func PrintInterrupts(option string, ops ...bool) {
irqStat, i := irqStat(), 0
i := 0
for _, interrupt := range irqStat {
switch option {
case "pairs":
Expand Down
158 changes: 8 additions & 150 deletions cmd/lspci/lspci.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@
package main

import (
"bufio"
"encoding/json"
"encoding/xml"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"text/tabwriter"

"github.com/TimRots/gutil-linux/pci"
)

const (
Expand Down Expand Up @@ -56,7 +55,6 @@ var (
xmloutput = flag.Bool("xml", false, "use XML output format")

PciDevices []PciDevice
errCnt = 0
)

func init() {
Expand Down Expand Up @@ -88,7 +86,7 @@ func main() {
}

func printDevices(opts string, params ...bool) {
PciDevices := ParsePciDevices()
PciDevices, err := pci.ParsePciDevices()

switch opts {
case "json":
Expand All @@ -100,9 +98,13 @@ func printDevices(opts string, params ...bool) {
default:
tabWriter(PciDevices)
}

if err != nil {
log.Fatal(err)
}
}

func tabWriter(PciDevices []PciDevice) {
func tabWriter(PciDevices []pci.PciDevice) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
defer w.Flush()

Expand Down Expand Up @@ -157,147 +159,3 @@ func tabWriter(PciDevices []PciDevice) {
}
}
}

func readFromFile(f string, w int) string {
var value []string

file, err := os.Open(f)
if err != nil {
log.Fatal(err)
}
defer file.Close()

if w == 0 {
w = 1
}
scanner, i, w := bufio.NewScanner(file), 0, w
for scanner.Scan() {
for _, word := range regexp.MustCompile(`[\S]+`).FindAllString(scanner.Text(), -1) {
switch i++; {
case i == w:
value = append(value, string(word))
}
return strings.Join(value, " ")
}
}
return ""
}

func Lookup(searchType, ven, dev, class, subclass string) string {
var found bool = false

// Open pci.ids
f, err := os.Open("./pci.ids")
if err != nil {
errCnt++
if errCnt < 2 {
fmt.Println("Error: Cannot open pci.ids file, defaulting to numeric")
}
*numeric = true
return ""
}
// close pci.ids file when done
defer func() {
if err := f.Close(); err != nil {
log.Fatal(err)
}
}()

scanner := bufio.NewScanner(f)
for scanner.Scan() {
switch searchType {
case "vendor": // Return first occurence of ven that does not have a \t prefix.
if strings.Contains(scanner.Text(), ven) && !strings.HasPrefix(scanner.Text(), "\t") {
return strings.TrimLeft(scanner.Text(), ven+" ")
}

case "device": // Return first occurence of dev after vendor is found
if strings.Contains(scanner.Text(), ven) && !strings.HasPrefix(scanner.Text(), "\t") {
found = true
}
if strings.HasPrefix(scanner.Text(), "\t"+dev) && found {
return strings.TrimLeft(scanner.Text(), "\t\t"+dev+" ")
}

case "class": // Split class (eg: 0600), search for "C 06" and return first occurence of "\t\t00" therafter.
if strings.Contains(scanner.Text(), "C"+" "+class[0:2]) && !strings.HasPrefix(scanner.Text(), "\t") {
found = true
}
if strings.HasPrefix(scanner.Text(), "\t"+class[2:4]) && found {
return strings.TrimLeft(scanner.Text(), "\t\t"+class+" ")

}
case "subsystem": // Match and return line "\t\t vendor subsystem_device"
if strings.Contains(scanner.Text(), ven+" "+subclass) {
return strings.TrimLeft(scanner.Text(), "\t\t"+ven+" "+subclass)
}
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
return "Unknown " + searchType
}

func ParsePciDevices() []PciDevice {
var devices []string

read := func(bus, filename string) string {
return readFromFile(filepath.Join(PATH_SYS_BUS_PCI_DEVICES, bus, filename), 1)
}

lookupKernelDriver := func(bus string) string {
read := readFromFile(filepath.Join(PATH_SYS_DEVICES_PCI+bus[0:7], bus, "uevent"), 1)
if strings.Contains(read, "DRIVER=") {
return read[7:]
}
return ""
}

// Find all devices in /sys/bus/pci/devices/ and append each device to devices[]
if err := filepath.Walk(
PATH_SYS_BUS_PCI_DEVICES,
func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
devices = append(devices, info.Name())
}
return nil
}); err != nil {
log.Fatal(err)
}

// Iterate over each bus and parse & append values to PciDevices[]
for _, bus := range devices {
dev := read(bus, "device")[2:6]
ven := read(bus, "vendor")[2:6]
class := read(bus, "class")[2:6]
subDev := read(bus, "subsystem_device")[2:6]
subVen := read(bus, "subsystem_vendor")[2:6]
mod := read(bus, "modalias")
irq := read(bus, "irq")
rev := read(bus, "revision")[2:4]

venName := Lookup("vendor", ven, "", "", "")
devName := Lookup("device", ven, dev, "", "")
devClass := Lookup("class", "", "", class, "")

subSys := ""
if subVen != "0000" {
subSys = Lookup("subsystem", ven, "", "", subDev)
}

kernelDriver := lookupKernelDriver(bus)

if !*showdomainnumbers && !*jsonoutput {
bus = strings.TrimPrefix(bus, "0000:")
}

PciDevices = append(PciDevices,
PciDevice{
bus, ven, dev, class, subVen, subDev, irq, rev,
venName, devName, devClass, subSys, "", mod, kernelDriver,
},
)
}
return PciDevices
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/TimRots/gutil-linux

go 1.20
66 changes: 66 additions & 0 deletions irq/irq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package irq

import (
"bufio"
"os"
"regexp"
"sort"
"strconv"
"strings"
)

const PATH_PROC_INTERRUPTS = "/proc/interrupts"

type Interrupt struct {
Irq string
Total int
Name string
}

func IrqStat() (Interrupts []Interrupt, err error) {
var f *os.File
if f, err = os.Open(PATH_PROC_INTERRUPTS); err != nil {
return
}
defer f.Close()

var (
active_cpu_count int
irq string
name []string
)
scanner := bufio.NewScanner(f)
for ln := 0; scanner.Scan(); ln++ {
if ln == 0 {
// First line shows the active cpus
active_cpu_count = strings.Count(scanner.Text(), "CPU")
continue
}

// Match all non-whitespace character sequences
i, total, words := 0, 0, regexp.MustCompile(`[\S]+`).FindAllString(scanner.Text(), -1)
for _, word := range words {
// As the lines are variadic in length we use active_cpu_count and
// i count to determine how to parse the value based on the word its position.
i++
switch {
case i == 1:
irq = strings.ReplaceAll(string(word), ":", "")
case i <= active_cpu_count+1:
numint, _ := strconv.Atoi(word)
total += numint
case i > active_cpu_count+1:
name = append(name, string(word))
}
}
Interrupts = append(Interrupts, Interrupt{irq, total, strings.Join(name, " ")})
irq, name = "", nil
}

// Sort slice by number
sort.Slice(Interrupts, func(y, z int) bool {
return Interrupts[y].Total > Interrupts[z].Total
})

return
}
Loading

0 comments on commit 0d44b4a

Please sign in to comment.