Skip to content

Commit

Permalink
support JSON and YAML serialization
Browse files Browse the repository at this point in the history
Adds support for serializing the primary XXXInfo structs in `ghw` as
JSON and YAML.

Each `ghw.XXXInfo` struct now has a `YAMLString()` and `JSONString()`
method that returns a string with the struct's information serialized to
that format.

The `ghwc` client has been enhanced with a `--format` CLI option that
allows users to print the hardware information in human-readable (the
default), JSON or YAML format:

```
[jaypipes@uberbox ghw]$ go run cmd/ghwc/main.go -f yaml memory 2>/dev/null
memory:
  supported_page_sizes:
  - 1073741824
  - 2097152
  total_physical_bytes: 25263415296
  total_usable_bytes: 25263415296
[jaypipes@uberbox ghw]$ go run cmd/ghwc/main.go -f json memory 2>/dev/null
{"memory":{"total_physical_bytes":25263415296,"total_usable_bytes":25263415296,"supported_page_sizes":[1073741824,2097152]}}
```

The `--format json` CLI option can be paired with a `--pretty` CLI
option to provide indented output:

```
[jaypipes@uberbox ghw]$ go run cmd/ghwc/main.go -f json --pretty memory 2>/dev/null
{
  "memory": {
    "total_physical_bytes": 25263415296,
    "total_usable_bytes": 25263415296,
    "supported_page_sizes": [
      1073741824,
      2097152
    ]
  }
}
```

Issue #78
  • Loading branch information
jaypipes committed Jan 30, 2019
1 parent 307c1c7 commit 4b46578
Show file tree
Hide file tree
Showing 25 changed files with 539 additions and 174 deletions.
34 changes: 14 additions & 20 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"

[[constraint]]
name = "gopkg.in/yaml.v2"
version = "2.2.2"

[[constraint]]
name = "github.com/ghodss/yaml"
version = "1.0.0"
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ information about the host computer:
* [Network](#network)
* [PCI](#pci)
* [GPU](#gpu)

* [YAML and JSON serialization](#serialization)

### Overriding the root mountpoint `ghw` uses

Expand Down Expand Up @@ -790,6 +790,48 @@ information
`ghw.TopologyNode` struct if you'd like to dig deeper into the NUMA/topology
subsystem

## Serialization

All of the `ghw` `XXXInfo` structs -- e.g. `ghw.CPUInfo` -- have two methods
for producing a serialized JSON or YAML string representation of the contained
information:

* `JSONString()` returns a string containing the information serialized into
JSON. It accepts a single boolean parameter indicating whether to use
indentation when outputting the string
* `YAMLString()` returns a string containing the information serialized into
YAML

```go
package main

import (
"fmt"

"github.com/jaypipes/ghw"
)

func main() {
mem, err := ghw.Memory()
if err != nil {
fmt.Printf("Error getting memory info: %v", err)
}

fmt.Printf("%s", mem.YAMLString())
}
```

the above example code prints the following out on my local workstation:

```
memory:
supported_page_sizes:
- 1073741824
- 2097152
total_physical_bytes: 25263415296
total_usable_bytes: 25263415296
```

## Developers

Contributions to `ghw` are welcomed! Fork the repo on GitHub and submit a pull
Expand Down
38 changes: 38 additions & 0 deletions architecture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package ghw

import "strings"

// Architecture describes the overall hardware architecture. It can be either
// Symmetric Multi-Processor (SMP) or Non-Uniform Memory Access (NUMA)
type Architecture int

const (
// SMP is a Symmetric Multi-Processor system
ARCHITECTURE_SMP Architecture = iota
// NUMA is a Non-Uniform Memory Access system
ARCHITECTURE_NUMA
)

var (
architectureString = map[Architecture]string{
ARCHITECTURE_SMP: "SMP",
ARCHITECTURE_NUMA: "NUMA",
}
)

func (a Architecture) String() string {
return architectureString[a]
}

// NOTE(jaypipes): since serialized output is as "official" as we're going to
// get, let's lowercase the string output when serializing, in order to
// "normalize" the expected serialized output
func (a Architecture) MarshalJSON() ([]byte, error) {
return []byte("\"" + strings.ToLower(a.String()) + "\""), nil
}
63 changes: 42 additions & 21 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,38 @@ import (
// Disk describes a single disk drive on the host system. Disk drives provide
// raw block storage resources.
type Disk struct {
Name string
SizeBytes uint64
PhysicalBlockSizeBytes uint64
BusType BusType
BusPath string
NUMANodeID int
Vendor string
Model string
SerialNumber string
WWN string
Partitions []*Partition
Name string `json:"name"`
SizeBytes uint64 `json:"size_bytes"`
PhysicalBlockSizeBytes uint64 `json:"physical_block_size_bytes"`
BusType BusType `json:"bus_type"`
BusPath string `json:"bus_path"`
// TODO(jaypipes): Convert this to a TopologyNode struct pointer and then
// add to serialized output as "numa_node,omitempty"
NUMANodeID int `json:"-"`
Vendor string `json:"vendor"`
Model string `json:"model"`
SerialNumber string `json:"serial_number"`
WWN string `json:"wwn"`
Partitions []*Partition `json:"partitions"`
}

// Partition describes a logical division of a Disk.
type Partition struct {
Disk *Disk
Name string
Label string
MountPoint string
SizeBytes uint64
Type string
IsReadOnly bool
Disk *Disk `json:"-"`
Name string `json:"name"`
Label string `json:"label"`
MountPoint string `json:"mount_point"`
SizeBytes uint64 `json:"size_bytes"`
Type string `json:"type"`
IsReadOnly bool `json:"read_only"`
}

// BlockInfo describes all disk drives and partitions in the host system.
type BlockInfo struct {
TotalPhysicalBytes uint64
Disks []*Disk
Partitions []*Partition
// TODO(jaypipes): Deprecate this field and replace with TotalSizeBytes
TotalPhysicalBytes uint64 `json:"total_size_bytes"`
Disks []*Disk `json:"disks"`
Partitions []*Partition `json:"-"`
}

// Block returns a BlockInfo struct that describes the block storage resources
Expand Down Expand Up @@ -141,3 +144,21 @@ func (p *Partition) String() string {
mountStr,
)
}

// simple private struct used to encapsulate block information in a top-level
// "block" YAML/JSON map/object key
type blockPrinter struct {
Info *BlockInfo `json:"block" yaml:"block"`
}

// YAMLString returns a string with the block information formatted as YAML
// under a top-level "block:" key
func (i *BlockInfo) YAMLString() string {
return safeYAML(blockPrinter{i})
}

// JSONString returns a string with the block information formatted as JSON
// under a top-level "block:" key
func (i *BlockInfo) JSONString(indent bool) string {
return safeJSON(blockPrinter{i}, indent)
}
13 changes: 11 additions & 2 deletions bus_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package ghw

import "strings"

type BusType int

const (
Expand All @@ -18,7 +20,7 @@ const (
)

var (
BusTypeString = map[BusType]string{
busTypeString = map[BusType]string{
BUS_TYPE_UNKNOWN: "Unknown",
BUS_TYPE_IDE: "IDE",
BUS_TYPE_PCI: "PCI",
Expand All @@ -29,5 +31,12 @@ var (
)

func (bt BusType) String() string {
return BusTypeString[bt]
return busTypeString[bt]
}

// NOTE(jaypipes): since serialized output is as "official" as we're going to
// get, let's lowercase the string output when serializing, in order to
// "normalize" the expected serialized output
func (bt BusType) MarshalJSON() ([]byte, error) {
return []byte("\"" + strings.ToLower(bt.String()) + "\""), nil
}
19 changes: 13 additions & 6 deletions cmd/ghwc/commands/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ func showBlock(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "error getting block device info")
}

fmt.Printf("%v\n", block)

for _, disk := range block.Disks {
fmt.Printf(" %v\n", disk)
for _, part := range disk.Partitions {
fmt.Printf(" %v\n", part)
switch outputFormat {
case outputFormatHuman:
fmt.Printf("%v\n", block)

for _, disk := range block.Disks {
fmt.Printf(" %v\n", disk)
for _, part := range disk.Partitions {
fmt.Printf(" %v\n", part)
}
}
case outputFormatJSON:
fmt.Printf("%s\n", block.JSONString(pretty))
case outputFormatYAML:
fmt.Printf("%s", block.YAMLString())
}
return nil
}
Expand Down
49 changes: 28 additions & 21 deletions cmd/ghwc/commands/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,31 +30,38 @@ func showCPU(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "error getting CPU info")
}

fmt.Printf("%v\n", cpu)
switch outputFormat {
case outputFormatHuman:
fmt.Printf("%v\n", cpu)

for _, proc := range cpu.Processors {
fmt.Printf(" %v\n", proc)
for _, core := range proc.Cores {
fmt.Printf(" %v\n", core)
}
if len(proc.Capabilities) > 0 {
// pretty-print the (large) block of capability strings into rows
// of 6 capability strings
rows := int(math.Ceil(float64(len(proc.Capabilities)) / float64(6)))
for row := 1; row < rows; row = row + 1 {
rowStart := (row * 6) - 1
rowEnd := int(math.Min(float64(rowStart+6), float64(len(proc.Capabilities))))
rowElems := proc.Capabilities[rowStart:rowEnd]
capStr := strings.Join(rowElems, " ")
if row == 1 {
fmt.Printf(" capabilities: [%s\n", capStr)
} else if rowEnd < len(proc.Capabilities) {
fmt.Printf(" %s\n", capStr)
} else {
fmt.Printf(" %s]\n", capStr)
for _, proc := range cpu.Processors {
fmt.Printf(" %v\n", proc)
for _, core := range proc.Cores {
fmt.Printf(" %v\n", core)
}
if len(proc.Capabilities) > 0 {
// pretty-print the (large) block of capability strings into rows
// of 6 capability strings
rows := int(math.Ceil(float64(len(proc.Capabilities)) / float64(6)))
for row := 1; row < rows; row = row + 1 {
rowStart := (row * 6) - 1
rowEnd := int(math.Min(float64(rowStart+6), float64(len(proc.Capabilities))))
rowElems := proc.Capabilities[rowStart:rowEnd]
capStr := strings.Join(rowElems, " ")
if row == 1 {
fmt.Printf(" capabilities: [%s\n", capStr)
} else if rowEnd < len(proc.Capabilities) {
fmt.Printf(" %s\n", capStr)
} else {
fmt.Printf(" %s]\n", capStr)
}
}
}
}
case outputFormatJSON:
fmt.Printf("%s\n", cpu.JSONString(pretty))
case outputFormatYAML:
fmt.Printf("%s", cpu.YAMLString())
}
return nil
}
Expand Down
Loading

0 comments on commit 4b46578

Please sign in to comment.