(v0.1.1) Automated packaging of release by Packagr
This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
#! /bin/sh
|
||||
|
||||
export GO_PACKAGES=$(go list ./... | grep -v /vendor/)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
vendor/
|
||||
coverage*.*
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
language: go
|
||||
# Using sudo triggers a real virtual machine as opposed to a container, which
|
||||
# allows ghw to actually determine things like host memory or block storage...
|
||||
sudo: required
|
||||
script:
|
||||
- source ./.get-go-packages.sh
|
||||
- go test -v $GO_PACKAGES
|
||||
- go run cmd/ghwc/main.go
|
||||
env:
|
||||
- GHW_TESTING_SKIP_GPU=1
|
||||
matrix:
|
||||
include:
|
||||
# On Go 1.10 and Go 1.11, use dep to ensure dependencies before running go
|
||||
# test.
|
||||
- os: linux
|
||||
go: "1.10"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
- os: linux
|
||||
go: "1.11"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
# On Go >=1.12, use go modules to ensure dependencies instead of dep
|
||||
- os: linux
|
||||
go: "1.12"
|
||||
env: GO111MODULE=on
|
||||
- os: linux
|
||||
go: "1.13"
|
||||
env: GO111MODULE=on
|
||||
- os: linux
|
||||
go: "1.14.x"
|
||||
env: GO111MODULE=on
|
||||
|
||||
# Tests that ghw builds on MacOSX (even though there is currently only
|
||||
# support for block devices)
|
||||
#- os: osx
|
||||
# go: "1.12"
|
||||
# env: GO111MODULE=on
|
||||
#- os: osx
|
||||
# go: "1.13"
|
||||
# env: GO111MODULE=on
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
FROM golang:1.13-stretch as builder
|
||||
WORKDIR /go/src/github.com/jaypipes/ghw
|
||||
|
||||
# Force the go compiler to use modules.
|
||||
ENV GO111MODULE=on
|
||||
ENV GOPROXY=direct
|
||||
|
||||
# go.mod and go.sum go into their own layers.
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
|
||||
# This ensures `go mod download` happens only when go.mod and go.sum change.
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build -o ghwc ./cmd/ghwc/
|
||||
|
||||
FROM alpine:3.7
|
||||
RUN apk add --no-cache ethtool
|
||||
|
||||
WORKDIR /bin
|
||||
|
||||
COPY --from=builder /go/src/github.com/jaypipes/ghw/ghwc /bin
|
||||
|
||||
CMD ghwc
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:49ab40aecc2c09b5eb0a277716f17958c502dc634d31eff3c345821320b1af31"
|
||||
name = "github.com/jaypipes/pcidb"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "4d488f78747b8369c9f5bb93462241a30d178059"
|
||||
version = "0.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:096a8a9182648da3d00ff243b88407838902b6703fc12657f76890e08d1899bf"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a1403cc8a94b8d7956ee5e9694badef0e7b051af289caad1cf668331e3ffa4f6"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0a52bcb568386d98f4894575d53ce3e456f56471de6897bb8b9de13c33d9340e"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
|
||||
version = "v2.2.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e43acd1190dde7223727f1f8aeac537156d33bf87776d5637e672e1c6b354be4"
|
||||
name = "howett.net/plist"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876"
|
||||
source = "github.com/DHowett/go-plist"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/ghodss/yaml",
|
||||
"github.com/jaypipes/pcidb",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/spf13/cobra",
|
||||
"howett.net/plist",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/jaypipes/pcidb"
|
||||
version = "0.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "howett.net/plist"
|
||||
source = "github.com/DHowett/go-plist"
|
||||
# pinning a sha since as yet no release has ever been made
|
||||
revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/ghodss/yaml"
|
||||
version = "1.0.0"
|
||||
|
||||
[[override]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
version = "2.2.2"
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
VENDOR := vendor
|
||||
PKGS := $(shell go list ./... | grep -v /$(VENDOR)/)
|
||||
SRC = $(shell find . -type f -name '*.go' -not -path "*/$(VENDOR)/*")
|
||||
BIN_DIR := $(GOPATH)/bin
|
||||
DEP := $(BIN_DIR)/dep
|
||||
GOMETALINTER := $(BIN_DIR)/gometalinter
|
||||
|
||||
.PHONY: test
|
||||
test: vet
|
||||
go test $(PKGS)
|
||||
|
||||
$(DEP):
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
|
||||
.PHONY: dep
|
||||
dep: $(DEP)
|
||||
$(DEP) ensure
|
||||
|
||||
$(GOMETALINTER):
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
$(GOMETALINTER) --install &> /dev/null
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GOMETALINTER)
|
||||
$(GOMETALINTER) ./... --vendor
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@echo "Running gofmt on all sources..."
|
||||
@gofmt -s -l -w $(SRC)
|
||||
|
||||
.PHONY: fmtcheck
|
||||
fmtcheck:
|
||||
@bash -c "diff -u <(echo -n) <(gofmt -d $(SRC))"
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet $(PKGS)
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
$(shell [ -e coverage.out ] && rm coverage.out)
|
||||
@echo "mode: count" > coverage-all.out
|
||||
$(foreach pkg,$(PKGS),\
|
||||
go test -coverprofile=coverage.out -covermode=count $(pkg);\
|
||||
tail -n +2 coverage.out >> coverage-all.out;)
|
||||
go tool cover -html=coverage-all.out -o=coverage-all.html
|
||||
+1168
File diff suppressed because it is too large
Load Diff
+38
@@ -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
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// 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 "fmt"
|
||||
|
||||
// BaseboardInfo defines baseboard release information
|
||||
type BaseboardInfo struct {
|
||||
AssetTag string `json:"asset_tag"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Vendor string `json:"vendor"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (i *BaseboardInfo) String() string {
|
||||
|
||||
vendorStr := ""
|
||||
if i.Vendor != "" {
|
||||
vendorStr = " vendor=" + i.Vendor
|
||||
}
|
||||
serialStr := ""
|
||||
if i.SerialNumber != "" && i.SerialNumber != UNKNOWN {
|
||||
serialStr = " serial=" + i.SerialNumber
|
||||
}
|
||||
versionStr := ""
|
||||
if i.Version != "" {
|
||||
versionStr = " version=" + i.Version
|
||||
}
|
||||
|
||||
res := fmt.Sprintf(
|
||||
"baseboard%s%s%s",
|
||||
vendorStr,
|
||||
serialStr,
|
||||
versionStr,
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// Baseboard returns a pointer to a BaseboardInfo struct containing information
|
||||
// about the host's baseboard
|
||||
func Baseboard(opts ...*WithOption) (*BaseboardInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &BaseboardInfo{}
|
||||
if err := ctx.baseboardFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate baseboard information in a top-level
|
||||
// "baseboard" YAML/JSON map/object key
|
||||
type baseboardPrinter struct {
|
||||
Info *BaseboardInfo `json:"baseboard"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the baseboard information formatted as YAML
|
||||
// under a top-level "dmi:" key
|
||||
func (info *BaseboardInfo) YAMLString() string {
|
||||
return safeYAML(baseboardPrinter{info})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the baseboard information formatted as JSON
|
||||
// under a top-level "baseboard:" key
|
||||
func (info *BaseboardInfo) JSONString(indent bool) string {
|
||||
return safeJSON(baseboardPrinter{info}, indent)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package ghw
|
||||
|
||||
func (ctx *context) baseboardFillInfo(info *BaseboardInfo) error {
|
||||
info.AssetTag = ctx.dmiItem("board_asset_tag")
|
||||
info.SerialNumber = ctx.dmiItem("board_serial")
|
||||
info.Vendor = ctx.dmiItem("board_vendor")
|
||||
info.Version = ctx.dmiItem("board_version")
|
||||
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) baseboardFillInfo(info *BaseboardInfo) error {
|
||||
return errors.New("baseboardFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// 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 "github.com/StackExchange/wmi"
|
||||
|
||||
const wqlBaseboard = "SELECT Manufacturer, SerialNumber, Tag, Version FROM Win32_BaseBoard"
|
||||
|
||||
type win32Baseboard struct {
|
||||
Manufacturer *string
|
||||
SerialNumber *string
|
||||
Tag *string
|
||||
Version *string
|
||||
}
|
||||
|
||||
func (ctx *context) baseboardFillInfo(info *BaseboardInfo) error {
|
||||
// Getting data from WMI
|
||||
var win32BaseboardDescriptions []win32Baseboard
|
||||
if err := wmi.Query(wqlBaseboard, &win32BaseboardDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(win32BaseboardDescriptions) > 0 {
|
||||
info.AssetTag = *win32BaseboardDescriptions[0].Tag
|
||||
info.SerialNumber = *win32BaseboardDescriptions[0].SerialNumber
|
||||
info.Vendor = *win32BaseboardDescriptions[0].Manufacturer
|
||||
info.Version = *win32BaseboardDescriptions[0].Version
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// 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 "fmt"
|
||||
|
||||
// BIOSInfo defines BIOS release information
|
||||
type BIOSInfo struct {
|
||||
Vendor string `json:"vendor"`
|
||||
Version string `json:"version"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
func (i *BIOSInfo) String() string {
|
||||
|
||||
vendorStr := ""
|
||||
if i.Vendor != "" {
|
||||
vendorStr = " vendor=" + i.Vendor
|
||||
}
|
||||
versionStr := ""
|
||||
if i.Version != "" {
|
||||
versionStr = " version=" + i.Version
|
||||
}
|
||||
dateStr := ""
|
||||
if i.Date != "" && i.Date != UNKNOWN {
|
||||
dateStr = " date=" + i.Date
|
||||
}
|
||||
|
||||
res := fmt.Sprintf(
|
||||
"bios%s%s%s",
|
||||
vendorStr,
|
||||
versionStr,
|
||||
dateStr,
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// BIOS returns a pointer to a BIOSInfo struct containing information
|
||||
// about the host's BIOS
|
||||
func BIOS(opts ...*WithOption) (*BIOSInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &BIOSInfo{}
|
||||
if err := ctx.biosFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate BIOS information in a top-level
|
||||
// "bios" YAML/JSON map/object key
|
||||
type biosPrinter struct {
|
||||
Info *BIOSInfo `json:"bios"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the BIOS information formatted as YAML
|
||||
// under a top-level "dmi:" key
|
||||
func (info *BIOSInfo) YAMLString() string {
|
||||
return safeYAML(biosPrinter{info})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the BIOS information formatted as JSON
|
||||
// under a top-level "bios:" key
|
||||
func (info *BIOSInfo) JSONString(indent bool) string {
|
||||
return safeJSON(biosPrinter{info}, indent)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package ghw
|
||||
|
||||
func (ctx *context) biosFillInfo(info *BIOSInfo) error {
|
||||
info.Vendor = ctx.dmiItem("bios_vendor")
|
||||
info.Version = ctx.dmiItem("bios_version")
|
||||
info.Date = ctx.dmiItem("bios_date")
|
||||
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) biosFillInfo(info *BIOSInfo) error {
|
||||
return errors.New("biosFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// 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 "github.com/StackExchange/wmi"
|
||||
|
||||
const wqlBIOS = "SELECT InstallDate, Manufacturer, Version FROM CIM_BIOSElement"
|
||||
|
||||
type win32BIOS struct {
|
||||
InstallDate *string
|
||||
Manufacturer *string
|
||||
Version *string
|
||||
}
|
||||
|
||||
func (ctx *context) biosFillInfo(info *BIOSInfo) error {
|
||||
// Getting data from WMI
|
||||
var win32BIOSDescriptions []win32BIOS
|
||||
if err := wmi.Query(wqlBIOS, &win32BIOSDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(win32BIOSDescriptions) > 0 {
|
||||
info.Vendor = *win32BIOSDescriptions[0].Manufacturer
|
||||
info.Version = *win32BIOSDescriptions[0].Version
|
||||
info.Date = *win32BIOSDescriptions[0].InstallDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DriveType describes the general category of drive device
|
||||
type DriveType int
|
||||
|
||||
const (
|
||||
DRIVE_TYPE_UNKNOWN DriveType = iota
|
||||
DRIVE_TYPE_HDD // Hard disk drive
|
||||
DRIVE_TYPE_FDD // Floppy disk drive
|
||||
DRIVE_TYPE_ODD // Optical disk drive
|
||||
DRIVE_TYPE_SSD // Solid-state drive
|
||||
)
|
||||
|
||||
var (
|
||||
driveTypeString = map[DriveType]string{
|
||||
DRIVE_TYPE_UNKNOWN: "Unknown",
|
||||
DRIVE_TYPE_HDD: "HDD",
|
||||
DRIVE_TYPE_FDD: "FDD",
|
||||
DRIVE_TYPE_ODD: "ODD",
|
||||
DRIVE_TYPE_SSD: "SSD",
|
||||
}
|
||||
)
|
||||
|
||||
func (dt DriveType) String() string {
|
||||
return driveTypeString[dt]
|
||||
}
|
||||
|
||||
// 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 (dt DriveType) MarshalJSON() ([]byte, error) {
|
||||
return []byte("\"" + strings.ToLower(dt.String()) + "\""), nil
|
||||
}
|
||||
|
||||
// StorageController is a category of block storage controller/driver. It
|
||||
// represents more of the physical hardware interface than the storage
|
||||
// protocol, which represents more of the software interface.
|
||||
//
|
||||
// See discussion on https://github.com/jaypipes/ghw/issues/117
|
||||
type StorageController int
|
||||
|
||||
const (
|
||||
STORAGE_CONTROLLER_UNKNOWN StorageController = iota
|
||||
STORAGE_CONTROLLER_IDE // Integrated Drive Electronics
|
||||
STORAGE_CONTROLLER_SCSI // Small computer system interface
|
||||
STORAGE_CONTROLLER_NVME // Non-volatile Memory Express
|
||||
STORAGE_CONTROLLER_VIRTIO // Virtualized storage controller/driver
|
||||
STORAGE_CONTROLLER_MMC // Multi-media controller (used for mobile phone storage devices)
|
||||
)
|
||||
|
||||
var (
|
||||
storageControllerString = map[StorageController]string{
|
||||
STORAGE_CONTROLLER_UNKNOWN: "Unknown",
|
||||
STORAGE_CONTROLLER_IDE: "IDE",
|
||||
STORAGE_CONTROLLER_SCSI: "SCSI",
|
||||
STORAGE_CONTROLLER_NVME: "NVMe",
|
||||
STORAGE_CONTROLLER_VIRTIO: "virtio",
|
||||
STORAGE_CONTROLLER_MMC: "MMC",
|
||||
}
|
||||
)
|
||||
|
||||
func (sc StorageController) String() string {
|
||||
return storageControllerString[sc]
|
||||
}
|
||||
|
||||
// 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 (sc StorageController) MarshalJSON() ([]byte, error) {
|
||||
return []byte("\"" + strings.ToLower(sc.String()) + "\""), nil
|
||||
}
|
||||
|
||||
// Disk describes a single disk drive on the host system. Disk drives provide
|
||||
// raw block storage resources.
|
||||
type Disk struct {
|
||||
Name string `json:"name"`
|
||||
SizeBytes uint64 `json:"size_bytes"`
|
||||
PhysicalBlockSizeBytes uint64 `json:"physical_block_size_bytes"`
|
||||
DriveType DriveType `json:"drive_type"`
|
||||
IsRemovable bool `json:"removable"`
|
||||
StorageController StorageController `json:"storage_controller"`
|
||||
// NOTE(jaypipes): BusType is DEPRECATED. Use the DriveType and
|
||||
// StorageController fields instead
|
||||
BusType BusType `json:"-"`
|
||||
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"`
|
||||
// TODO(jaypipes): Add PCI field for accessing PCI device information
|
||||
// PCI *PCIDevice `json:"pci"`
|
||||
}
|
||||
|
||||
// Partition describes a logical division of a Disk.
|
||||
type Partition struct {
|
||||
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 {
|
||||
// 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
|
||||
// of the host system.
|
||||
func Block(opts ...*WithOption) (*BlockInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &BlockInfo{}
|
||||
if err := ctx.blockFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (i *BlockInfo) String() string {
|
||||
tpbs := UNKNOWN
|
||||
if i.TotalPhysicalBytes > 0 {
|
||||
tpb := i.TotalPhysicalBytes
|
||||
unit, unitStr := unitWithString(int64(tpb))
|
||||
tpb = uint64(math.Ceil(float64(tpb) / float64(unit)))
|
||||
tpbs = fmt.Sprintf("%d%s", tpb, unitStr)
|
||||
}
|
||||
dplural := "disks"
|
||||
if len(i.Disks) == 1 {
|
||||
dplural = "disk"
|
||||
}
|
||||
return fmt.Sprintf("block storage (%d %s, %s physical storage)",
|
||||
len(i.Disks), dplural, tpbs)
|
||||
}
|
||||
|
||||
func (d *Disk) String() string {
|
||||
sizeStr := UNKNOWN
|
||||
if d.SizeBytes > 0 {
|
||||
size := d.SizeBytes
|
||||
unit, unitStr := unitWithString(int64(size))
|
||||
size = uint64(math.Ceil(float64(size) / float64(unit)))
|
||||
sizeStr = fmt.Sprintf("%d%s", size, unitStr)
|
||||
}
|
||||
atNode := ""
|
||||
if d.NUMANodeID >= 0 {
|
||||
atNode = fmt.Sprintf(" (node #%d)", d.NUMANodeID)
|
||||
}
|
||||
vendor := ""
|
||||
if d.Vendor != "" {
|
||||
vendor = " vendor=" + d.Vendor
|
||||
}
|
||||
model := ""
|
||||
if d.Model != UNKNOWN {
|
||||
model = " model=" + d.Model
|
||||
}
|
||||
serial := ""
|
||||
if d.SerialNumber != UNKNOWN {
|
||||
serial = " serial=" + d.SerialNumber
|
||||
}
|
||||
wwn := ""
|
||||
if d.WWN != UNKNOWN {
|
||||
wwn = " WWN=" + d.WWN
|
||||
}
|
||||
removable := ""
|
||||
if d.IsRemovable {
|
||||
removable = " removable=true"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s %s (%s) %s [@%s%s]%s%s%s%s%s",
|
||||
d.Name,
|
||||
d.DriveType.String(),
|
||||
sizeStr,
|
||||
d.StorageController.String(),
|
||||
d.BusPath,
|
||||
atNode,
|
||||
vendor,
|
||||
model,
|
||||
serial,
|
||||
wwn,
|
||||
removable,
|
||||
)
|
||||
}
|
||||
|
||||
func (p *Partition) String() string {
|
||||
typeStr := ""
|
||||
if p.Type != "" {
|
||||
typeStr = fmt.Sprintf("[%s]", p.Type)
|
||||
}
|
||||
mountStr := ""
|
||||
if p.MountPoint != "" {
|
||||
mountStr = fmt.Sprintf(" mounted@%s", p.MountPoint)
|
||||
}
|
||||
sizeStr := UNKNOWN
|
||||
if p.SizeBytes > 0 {
|
||||
size := p.SizeBytes
|
||||
unit, unitStr := unitWithString(int64(size))
|
||||
size = uint64(math.Ceil(float64(size) / float64(unit)))
|
||||
sizeStr = fmt.Sprintf("%d%s", size, unitStr)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s (%s) %s%s",
|
||||
p.Name,
|
||||
sizeStr,
|
||||
typeStr,
|
||||
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)
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type diskOrPartitionPlistNode struct {
|
||||
Content string
|
||||
DeviceIdentifier string
|
||||
DiskUUID string
|
||||
VolumeName string
|
||||
VolumeUUID string
|
||||
Size int64
|
||||
MountPoint string
|
||||
Partitions []diskOrPartitionPlistNode
|
||||
APFSVolumes []diskOrPartitionPlistNode
|
||||
}
|
||||
|
||||
type diskUtilListPlist struct {
|
||||
AllDisks []string
|
||||
AllDisksAndPartitions []diskOrPartitionPlistNode
|
||||
VolumesFromDisks []string
|
||||
WholeDisks []string
|
||||
}
|
||||
|
||||
type diskUtilInfoPlist struct {
|
||||
AESHardware bool // true
|
||||
Bootable bool // true
|
||||
BooterDeviceIdentifier string // disk1s2
|
||||
BusProtocol string // PCI-Express
|
||||
CanBeMadeBootable bool // false
|
||||
CanBeMadeBootableRequiresDestroy bool // false
|
||||
Content string // some-uuid-foo-bar
|
||||
DeviceBlockSize int64 // 4096
|
||||
DeviceIdentifier string // disk1s1
|
||||
DeviceNode string // /dev/disk1s1
|
||||
DeviceTreePath string // IODeviceTree:/PCI0@0/RP17@1B/ANS2@0/AppleANS2Controller
|
||||
DiskUUID string // some-uuid-foo-bar
|
||||
Ejectable bool // false
|
||||
EjectableMediaAutomaticUnderSoftwareControl bool // false
|
||||
EjectableOnly bool // false
|
||||
FilesystemName string // APFS
|
||||
FilesystemType string // apfs
|
||||
FilesystemUserVisibleName string // APFS
|
||||
FreeSpace int64 // 343975677952
|
||||
GlobalPermissionsEnabled bool // true
|
||||
IOKitSize int64 // 499963174912
|
||||
IORegistryEntryName string // Macintosh HD
|
||||
Internal bool // true
|
||||
MediaName string //
|
||||
MediaType string // Generic
|
||||
MountPoint string // /
|
||||
ParentWholeDisk string // disk1
|
||||
PartitionMapPartition bool // false
|
||||
RAIDMaster bool // false
|
||||
RAIDSlice bool // false
|
||||
RecoveryDeviceIdentifier string // disk1s3
|
||||
Removable bool // false
|
||||
RemovableMedia bool // false
|
||||
RemovableMediaOrExternalDevice bool // false
|
||||
SMARTStatus string // Verified
|
||||
Size int64 // 499963174912
|
||||
SolidState bool // true
|
||||
SupportsGlobalPermissionsDisable bool // true
|
||||
SystemImage bool // false
|
||||
TotalSize int64 // 499963174912
|
||||
VolumeAllocationBlockSize int64 // 4096
|
||||
VolumeName string // Macintosh HD
|
||||
VolumeSize int64 // 499963174912
|
||||
VolumeUUID string // some-uuid-foo-bar
|
||||
WholeDisk bool // false
|
||||
Writable bool // true
|
||||
WritableMedia bool // true
|
||||
WritableVolume bool // true
|
||||
// also has a SMARTDeviceSpecificKeysMayVaryNotGuaranteed dict with various info
|
||||
// NOTE: VolumeUUID sometimes == DiskUUID, but not always. So far Content is always a different UUID.
|
||||
}
|
||||
|
||||
type ioregPlist struct {
|
||||
// there's a lot more than just this...
|
||||
ModelNumber string `plist:"Model Number"`
|
||||
SerialNumber string `plist:"Serial Number"`
|
||||
VendorName string `plist:"Vendor Name"`
|
||||
}
|
||||
|
||||
func (ctx *context) getDiskUtilListPlist() (*diskUtilListPlist, error) {
|
||||
out, err := exec.Command("diskutil", "list", "-plist").Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "diskutil list failed")
|
||||
}
|
||||
|
||||
var data diskUtilListPlist
|
||||
if _, err := plist.Unmarshal(out, &data); err != nil {
|
||||
return nil, errors.Wrap(err, "diskutil list plist unmarshal failed")
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (ctx *context) getDiskUtilInfoPlist(device string) (*diskUtilInfoPlist, error) {
|
||||
out, err := exec.Command("diskutil", "info", "-plist", device).Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "diskutil info for %q failed", device)
|
||||
}
|
||||
|
||||
var data diskUtilInfoPlist
|
||||
if _, err := plist.Unmarshal(out, &data); err != nil {
|
||||
return nil, errors.Wrapf(err, "diskutil info plist unmarshal for %q failed", device)
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (ctx *context) getIoregPlist(ioDeviceTreePath string) (*ioregPlist, error) {
|
||||
name := path.Base(ioDeviceTreePath)
|
||||
|
||||
args := []string{
|
||||
"ioreg",
|
||||
"-a", // use XML output
|
||||
"-d", "1", // limit device tree output depth to root node
|
||||
"-r", // root device tree at matched node
|
||||
"-n", name, // match by name
|
||||
}
|
||||
out, err := exec.Command(args[0], args[1:]...).Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ioreg query for %q failed", ioDeviceTreePath)
|
||||
}
|
||||
if out == nil || len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var data []ioregPlist
|
||||
if _, err := plist.Unmarshal(out, &data); err != nil {
|
||||
return nil, errors.Wrapf(err, "ioreg unmarshal for %q failed", ioDeviceTreePath)
|
||||
}
|
||||
if len(data) != 1 {
|
||||
err := errors.Errorf("ioreg unmarshal resulted in %d I/O device tree nodes (expected 1)", len(data))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data[0], nil
|
||||
}
|
||||
|
||||
func (ctx *context) makePartition(disk, s diskOrPartitionPlistNode, isAPFS bool) (*Partition, error) {
|
||||
if s.Size < 0 {
|
||||
return nil, errors.Errorf("invalid size %q of partition %q", s.Size, s.DeviceIdentifier)
|
||||
}
|
||||
|
||||
var partType string
|
||||
if isAPFS {
|
||||
partType = "APFS Volume"
|
||||
} else {
|
||||
partType = s.Content
|
||||
}
|
||||
|
||||
info, err := ctx.getDiskUtilInfoPlist(s.DeviceIdentifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Partition{
|
||||
Disk: nil, // filled in later
|
||||
Name: s.DeviceIdentifier,
|
||||
Label: s.VolumeName,
|
||||
MountPoint: s.MountPoint,
|
||||
SizeBytes: uint64(s.Size),
|
||||
Type: partType,
|
||||
IsReadOnly: !info.WritableVolume,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// driveTypeFromPlist looks at the supplied property list struct and attempts to
|
||||
// determine the disk type
|
||||
func (ctx *context) driveTypeFromPlist(
|
||||
infoPlist *diskUtilInfoPlist,
|
||||
) DriveType {
|
||||
dt := DRIVE_TYPE_HDD
|
||||
if infoPlist.SolidState {
|
||||
dt = DRIVE_TYPE_SSD
|
||||
}
|
||||
// TODO(jaypipes): Figure out how to determine floppy and/or CD/optical
|
||||
// drive type on Mac
|
||||
return dt
|
||||
}
|
||||
|
||||
// storageControllerFromPlist looks at the supplied property list struct and
|
||||
// attempts to determine the storage controller in use for the device
|
||||
func (ctx *context) storageControllerFromPlist(
|
||||
infoPlist *diskUtilInfoPlist,
|
||||
) StorageController {
|
||||
sc := STORAGE_CONTROLLER_SCSI
|
||||
if strings.HasSuffix(infoPlist.DeviceTreePath, "IONVMeController") {
|
||||
sc = STORAGE_CONTROLLER_NVME
|
||||
}
|
||||
// TODO(jaypipes): I don't know if Mac even supports IDE controllers and
|
||||
// the "virtio" controller is libvirt-specific
|
||||
return sc
|
||||
}
|
||||
|
||||
// busTypeFromPlist looks at the supplied property list struct and attempts to
|
||||
// determine the bus type in use for the device
|
||||
func (ctx *context) busTypeFromPlist(
|
||||
infoPlist *diskUtilInfoPlist,
|
||||
) BusType {
|
||||
// TODO(jaypipes): Find out if Macs support any bus other than
|
||||
// PCIe... it doesn't seem like they do
|
||||
return BUS_TYPE_PCI
|
||||
}
|
||||
|
||||
func (ctx *context) blockFillInfo(info *BlockInfo) error {
|
||||
listPlist, err := ctx.getDiskUtilListPlist()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
info.TotalPhysicalBytes = 0
|
||||
info.Disks = make([]*Disk, 0, len(listPlist.AllDisksAndPartitions))
|
||||
info.Partitions = []*Partition{}
|
||||
|
||||
for _, disk := range listPlist.AllDisksAndPartitions {
|
||||
if disk.Size < 0 {
|
||||
return errors.Errorf("invalid size %q of disk %q", disk.Size, disk.DeviceIdentifier)
|
||||
}
|
||||
|
||||
infoPlist, err := ctx.getDiskUtilInfoPlist(disk.DeviceIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if infoPlist.DeviceBlockSize < 0 {
|
||||
return errors.Errorf("invalid block size %q of disk %q", infoPlist.DeviceBlockSize, disk.DeviceIdentifier)
|
||||
}
|
||||
|
||||
busPath := strings.TrimPrefix(infoPlist.DeviceTreePath, "IODeviceTree:")
|
||||
|
||||
ioregPlist, err := ctx.getIoregPlist(infoPlist.DeviceTreePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ioregPlist == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// The NUMA node & WWN don't seem to be reported by any tools available by default in macOS.
|
||||
diskReport := &Disk{
|
||||
Name: disk.DeviceIdentifier,
|
||||
SizeBytes: uint64(disk.Size),
|
||||
PhysicalBlockSizeBytes: uint64(infoPlist.DeviceBlockSize),
|
||||
DriveType: ctx.driveTypeFromPlist(infoPlist),
|
||||
IsRemovable: infoPlist.Removable,
|
||||
StorageController: ctx.storageControllerFromPlist(infoPlist),
|
||||
BusType: ctx.busTypeFromPlist(infoPlist),
|
||||
BusPath: busPath,
|
||||
NUMANodeID: -1,
|
||||
Vendor: ioregPlist.VendorName,
|
||||
Model: ioregPlist.ModelNumber,
|
||||
SerialNumber: ioregPlist.SerialNumber,
|
||||
WWN: "",
|
||||
Partitions: make([]*Partition, 0, len(disk.Partitions)+len(disk.APFSVolumes)),
|
||||
}
|
||||
|
||||
for _, partition := range disk.Partitions {
|
||||
part, err := ctx.makePartition(disk, partition, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
part.Disk = diskReport
|
||||
diskReport.Partitions = append(diskReport.Partitions, part)
|
||||
}
|
||||
for _, volume := range disk.APFSVolumes {
|
||||
part, err := ctx.makePartition(disk, volume, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
part.Disk = diskReport
|
||||
diskReport.Partitions = append(diskReport.Partitions, part)
|
||||
}
|
||||
|
||||
info.TotalPhysicalBytes += uint64(disk.Size)
|
||||
info.Disks = append(info.Disks, diskReport)
|
||||
info.Partitions = append(info.Partitions, diskReport.Partitions...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+652
@@ -0,0 +1,652 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
sectorSize = 512
|
||||
)
|
||||
|
||||
func (ctx *context) blockFillInfo(info *BlockInfo) error {
|
||||
info.Disks = ctx.disks()
|
||||
var tpb uint64
|
||||
for _, d := range info.Disks {
|
||||
tpb += d.SizeBytes
|
||||
}
|
||||
info.TotalPhysicalBytes = tpb
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiskPhysicalBlockSizeBytes has been deprecated in 0.2. Please use the
|
||||
// Disk.PhysicalBlockSizeBytes attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskPhysicalBlockSizeBytes(disk string) uint64 {
|
||||
msg := `
|
||||
The DiskPhysicalBlockSizeBytes() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.PhysicalBlockSizeBytes
|
||||
attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskPhysicalBlockSizeBytes(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskPhysicalBlockSizeBytes(disk string) uint64 {
|
||||
// We can find the sector size in Linux by looking at the
|
||||
// /sys/block/$DEVICE/queue/physical_block_size file in sysfs
|
||||
path := filepath.Join(ctx.pathSysBlock(), disk, "queue", "physical_block_size")
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// DiskSizeBytes has been deprecated in 0.2. Please use the Disk.SizeBytes
|
||||
// attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskSizeBytes(disk string) uint64 {
|
||||
msg := `
|
||||
The DiskSizeBytes() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.SizeBytes attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskSizeBytes(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskSizeBytes(disk string) uint64 {
|
||||
// We can find the number of 512-byte sectors by examining the contents of
|
||||
// /sys/block/$DEVICE/size and calculate the physical bytes accordingly.
|
||||
path := filepath.Join(ctx.pathSysBlock(), disk, "size")
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return size * sectorSize
|
||||
}
|
||||
|
||||
// DiskNUMANodeID has been deprecated in 0.2. Please use the Disk.NUMANodeID
|
||||
// attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskNUMANodeID(disk string) int {
|
||||
msg := `
|
||||
The DiskNUMANodeID() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.NUMANodeID attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskNUMANodeID(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskNUMANodeID(disk string) int {
|
||||
link, err := os.Readlink(filepath.Join(ctx.pathSysBlock(), disk))
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
for partial := link; strings.HasPrefix(partial, "../devices/"); partial = filepath.Base(partial) {
|
||||
if nodeContents, err := ioutil.ReadFile(filepath.Join(ctx.pathSysBlock(), partial, "numa_node")); err != nil {
|
||||
if nodeInt, err := strconv.Atoi(string(nodeContents)); err != nil {
|
||||
return nodeInt
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// DiskVendor has been deprecated in 0.2. Please use the Disk.Vendor attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskVendor(disk string) string {
|
||||
msg := `
|
||||
The DiskVendor() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.Vendor attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskVendor(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskVendor(disk string) string {
|
||||
// In Linux, the vendor for a disk device is found in the
|
||||
// /sys/block/$DEVICE/device/vendor file in sysfs
|
||||
path := filepath.Join(ctx.pathSysBlock(), disk, "device", "vendor")
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return UNKNOWN
|
||||
}
|
||||
return strings.TrimSpace(string(contents))
|
||||
}
|
||||
|
||||
func (ctx *context) udevInfo(disk string) (map[string]string, error) {
|
||||
// Get device major:minor numbers
|
||||
devNo, err := ioutil.ReadFile(filepath.Join(ctx.pathSysBlock(), disk, "dev"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Look up block device in udev runtime database
|
||||
udevID := "b" + strings.TrimSpace(string(devNo))
|
||||
udevBytes, err := ioutil.ReadFile(filepath.Join(ctx.pathRunUdevData(), udevID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
udevInfo := make(map[string]string)
|
||||
for _, udevLine := range strings.Split(string(udevBytes), "\n") {
|
||||
if strings.HasPrefix(udevLine, "E:") {
|
||||
if s := strings.SplitN(udevLine[2:], "=", 2); len(s) == 2 {
|
||||
udevInfo[s[0]] = s[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return udevInfo, nil
|
||||
}
|
||||
|
||||
// DiskModel has been deprecated in 0.2. Please use the Disk.Model attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskModel(disk string) string {
|
||||
msg := `
|
||||
The DiskModel() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the Disk.Model attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskModel(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskModel(disk string) string {
|
||||
info, err := ctx.udevInfo(disk)
|
||||
if err != nil {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
if model, ok := info["ID_MODEL"]; ok {
|
||||
return model
|
||||
}
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskSerialNumber has been deprecated in 0.2. Please use the Disk.SerialNumber attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskSerialNumber(disk string) string {
|
||||
msg := `
|
||||
The DiskSerialNumber() function has been DEPRECATED and will be removed in the
|
||||
1.0 release of ghw. Please use the Disk.SerialNumber attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskSerialNumber(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskSerialNumber(disk string) string {
|
||||
info, err := ctx.udevInfo(disk)
|
||||
if err != nil {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// There are two serial number keys, ID_SERIAL and ID_SERIAL_SHORT
|
||||
// The non-_SHORT version often duplicates vendor information collected elsewhere, so use _SHORT.
|
||||
if serial, ok := info["ID_SERIAL_SHORT"]; ok {
|
||||
return serial
|
||||
}
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskBusPath has been deprecated in 0.2. Please use the Disk.BusPath attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskBusPath(disk string) string {
|
||||
msg := `
|
||||
The DiskBusPath() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the Disk.BusPath attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskBusPath(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskBusPath(disk string) string {
|
||||
info, err := ctx.udevInfo(disk)
|
||||
if err != nil {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// There are two path keys, ID_PATH and ID_PATH_TAG.
|
||||
// The difference seems to be _TAG has funky characters converted to underscores.
|
||||
if path, ok := info["ID_PATH"]; ok {
|
||||
return path
|
||||
}
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskWWN has been deprecated in 0.2. Please use the Disk.WWN attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskWWN(disk string) string {
|
||||
msg := `
|
||||
The DiskWWN() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the Disk.WWN attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskWWN(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskWWN(disk string) string {
|
||||
info, err := ctx.udevInfo(disk)
|
||||
if err != nil {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// Trying ID_WWN_WITH_EXTENSION and falling back to ID_WWN is the same logic lsblk uses
|
||||
if wwn, ok := info["ID_WWN_WITH_EXTENSION"]; ok {
|
||||
return wwn
|
||||
}
|
||||
if wwn, ok := info["ID_WWN"]; ok {
|
||||
return wwn
|
||||
}
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskPartitions has been deprecated in 0.2. Please use the Disk.Partitions attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskPartitions(disk string) []*Partition {
|
||||
msg := `
|
||||
The DiskPartitions() function has been DEPRECATED and will be removed in the
|
||||
1.0 release of ghw. Please use the Disk.Partitions attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskPartitions(disk)
|
||||
}
|
||||
|
||||
// diskPartitions takes the name of a disk (note: *not* the path of the disk,
|
||||
// but just the name. In other words, "sda", not "/dev/sda" and "nvme0n1" not
|
||||
// "/dev/nvme0n1") and returns a slice of pointers to Partition structs
|
||||
// representing the partitions in that disk
|
||||
func (ctx *context) diskPartitions(disk string) []*Partition {
|
||||
out := make([]*Partition, 0)
|
||||
path := filepath.Join(ctx.pathSysBlock(), disk)
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
warn("failed to read disk partitions: %s\n", err)
|
||||
return out
|
||||
}
|
||||
for _, file := range files {
|
||||
fname := file.Name()
|
||||
if !strings.HasPrefix(fname, disk) {
|
||||
continue
|
||||
}
|
||||
size := ctx.partitionSizeBytes(disk, fname)
|
||||
mp, pt, ro := ctx.partitionInfo(fname)
|
||||
p := &Partition{
|
||||
Name: fname,
|
||||
SizeBytes: size,
|
||||
MountPoint: mp,
|
||||
Type: pt,
|
||||
IsReadOnly: ro,
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (ctx *context) diskIsRemovable(disk string) bool {
|
||||
path := filepath.Join(ctx.pathSysBlock(), disk, "removable")
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
removable := strings.TrimSpace(string(contents))
|
||||
if removable == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Disks has been deprecated in 0.2. Please use the BlockInfo.Disks attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func Disks() []*Disk {
|
||||
msg := `
|
||||
The Disks() function has been DEPRECATED and will be removed in the
|
||||
1.0 release of ghw. Please use the BlockInfo.Disks attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.disks()
|
||||
}
|
||||
|
||||
func (ctx *context) disks() []*Disk {
|
||||
// In Linux, we could use the fdisk, lshw or blockdev commands to list disk
|
||||
// information, however all of these utilities require root privileges to
|
||||
// run. We can get all of this information by examining the /sys/block
|
||||
// and /sys/class/block files
|
||||
disks := make([]*Disk, 0)
|
||||
files, err := ioutil.ReadDir(ctx.pathSysBlock())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, file := range files {
|
||||
dname := file.Name()
|
||||
if strings.HasPrefix(dname, "loop") {
|
||||
continue
|
||||
}
|
||||
|
||||
driveType, storageController, busType := diskTypes(dname)
|
||||
// TODO(jaypipes): Move this into diskTypes() once abstracting
|
||||
// diskIsRotational for ease of unit testing
|
||||
if !ctx.diskIsRotational(dname) {
|
||||
driveType = DRIVE_TYPE_SSD
|
||||
}
|
||||
size := ctx.diskSizeBytes(dname)
|
||||
pbs := ctx.diskPhysicalBlockSizeBytes(dname)
|
||||
busPath := ctx.diskBusPath(dname)
|
||||
node := ctx.diskNUMANodeID(dname)
|
||||
vendor := ctx.diskVendor(dname)
|
||||
model := ctx.diskModel(dname)
|
||||
serialNo := ctx.diskSerialNumber(dname)
|
||||
wwn := ctx.diskWWN(dname)
|
||||
removable := ctx.diskIsRemovable(dname)
|
||||
|
||||
d := &Disk{
|
||||
Name: dname,
|
||||
SizeBytes: size,
|
||||
PhysicalBlockSizeBytes: pbs,
|
||||
DriveType: driveType,
|
||||
IsRemovable: removable,
|
||||
StorageController: storageController,
|
||||
BusType: busType,
|
||||
BusPath: busPath,
|
||||
NUMANodeID: node,
|
||||
Vendor: vendor,
|
||||
Model: model,
|
||||
SerialNumber: serialNo,
|
||||
WWN: wwn,
|
||||
}
|
||||
|
||||
parts := ctx.diskPartitions(dname)
|
||||
// Map this Disk object into the Partition...
|
||||
for _, part := range parts {
|
||||
part.Disk = d
|
||||
}
|
||||
d.Partitions = parts
|
||||
|
||||
disks = append(disks, d)
|
||||
}
|
||||
|
||||
return disks
|
||||
}
|
||||
|
||||
// diskTypes returns the drive type, storage controller and bus type of a disk
|
||||
func diskTypes(dname string) (
|
||||
DriveType,
|
||||
StorageController,
|
||||
BusType,
|
||||
) {
|
||||
// The conditionals below which set the controller and drive type are
|
||||
// based on information listed here:
|
||||
// https://en.wikipedia.org/wiki/Device_file
|
||||
busType := BUS_TYPE_UNKNOWN
|
||||
driveType := DRIVE_TYPE_UNKNOWN
|
||||
storageController := STORAGE_CONTROLLER_UNKNOWN
|
||||
if strings.HasPrefix(dname, "fd") {
|
||||
driveType = DRIVE_TYPE_FDD
|
||||
} else if strings.HasPrefix(dname, "sd") {
|
||||
driveType = DRIVE_TYPE_HDD
|
||||
busType = BUS_TYPE_SCSI
|
||||
storageController = STORAGE_CONTROLLER_SCSI
|
||||
} else if strings.HasPrefix(dname, "hd") {
|
||||
driveType = DRIVE_TYPE_HDD
|
||||
busType = BUS_TYPE_IDE
|
||||
storageController = STORAGE_CONTROLLER_IDE
|
||||
} else if strings.HasPrefix(dname, "vd") {
|
||||
driveType = DRIVE_TYPE_HDD
|
||||
busType = BUS_TYPE_VIRTIO
|
||||
storageController = STORAGE_CONTROLLER_VIRTIO
|
||||
} else if strings.HasPrefix(dname, "nvme") {
|
||||
driveType = DRIVE_TYPE_SSD
|
||||
busType = BUS_TYPE_NVME
|
||||
storageController = STORAGE_CONTROLLER_NVME
|
||||
} else if strings.HasPrefix(dname, "sr") {
|
||||
driveType = DRIVE_TYPE_ODD
|
||||
busType = BUS_TYPE_SCSI
|
||||
storageController = STORAGE_CONTROLLER_SCSI
|
||||
} else if strings.HasPrefix(dname, "xvd") {
|
||||
driveType = DRIVE_TYPE_HDD
|
||||
busType = BUS_TYPE_SCSI
|
||||
storageController = STORAGE_CONTROLLER_SCSI
|
||||
} else if strings.HasPrefix(dname, "mmc") {
|
||||
driveType = DRIVE_TYPE_SSD
|
||||
busType = BUS_TYPE_UNKNOWN
|
||||
storageController = STORAGE_CONTROLLER_MMC
|
||||
}
|
||||
|
||||
return driveType, storageController, busType
|
||||
}
|
||||
|
||||
func (ctx *context) diskIsRotational(devName string) bool {
|
||||
path := filepath.Join(ctx.pathSysBlock(), devName, "queue", "rotational")
|
||||
contents := safeIntFromFile(path)
|
||||
return contents == 1
|
||||
}
|
||||
|
||||
// PartitionSizeBytes has been deprecated in 0.2. Please use the
|
||||
// Partition.SizeBytes attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionSizeBytes(part string) uint64 {
|
||||
msg := `
|
||||
The PartitionSizeBytes() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.SizeBytes attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
disk := strings.TrimPrefix(part, "/dev")
|
||||
if len(disk) < 3 {
|
||||
warn("PartitionSizeBytes: unknown disk %s, returning 0\n", disk)
|
||||
return 0
|
||||
}
|
||||
switch disk[0:2] {
|
||||
case "fd":
|
||||
case "sd":
|
||||
case "hd":
|
||||
case "vd":
|
||||
case "sr":
|
||||
case "mm":
|
||||
disk = disk[0:3]
|
||||
case "xv":
|
||||
disk = disk[0:4]
|
||||
case "nv":
|
||||
// NOTE(jaypipes): I'm putting this regex here instead of a const
|
||||
// because this function is the only thing that uses it and I'm
|
||||
// deprecating this function in 1.0
|
||||
// nvme0n1p2
|
||||
var regexNVMeDev = regexp.MustCompile(`^nvme\d+n\d+$`)
|
||||
matches := regexNVMeDev.FindSubmatch([]byte(disk))
|
||||
if len(matches) < 1 {
|
||||
warn("PartitionSizeBytes: unknown disk %s, returning 0\n", disk)
|
||||
return 0
|
||||
}
|
||||
disk = string(matches[0])
|
||||
}
|
||||
return ctx.partitionSizeBytes(disk, part)
|
||||
}
|
||||
|
||||
// partitionSizeBytes returns the size in bytes of the partition given a disk
|
||||
// name and a partition name. Note: disk name and partition name do *not*
|
||||
// contain any leading "/dev" parts. In other words, they are *names*, not
|
||||
// paths.
|
||||
func (ctx *context) partitionSizeBytes(disk string, part string) uint64 {
|
||||
path := filepath.Join(ctx.pathSysBlock(), disk, part, "size")
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return size * sectorSize
|
||||
}
|
||||
|
||||
// PartitionInfo has been deprecated in 0.2. Please use the Partition struct.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionInfo(part string) (string, string, bool) {
|
||||
msg := `
|
||||
The PartitionInfo() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition struct.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionInfo(part)
|
||||
}
|
||||
|
||||
// Given a full or short partition name, returns the mount point, the type of
|
||||
// the partition and whether it's readonly
|
||||
func (ctx *context) partitionInfo(part string) (string, string, bool) {
|
||||
// Allow calling PartitionInfo with either the full partition name
|
||||
// "/dev/sda1" or just "sda1"
|
||||
if !strings.HasPrefix(part, "/dev") {
|
||||
part = "/dev/" + part
|
||||
}
|
||||
|
||||
// /etc/mtab entries for mounted partitions look like this:
|
||||
// /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
|
||||
var r io.ReadCloser
|
||||
r, err := os.Open(ctx.pathEtcMtab())
|
||||
if err != nil {
|
||||
return "", "", true
|
||||
}
|
||||
defer safeClose(r)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
entry := parseMtabEntry(line)
|
||||
if entry == nil || entry.Partition != part {
|
||||
continue
|
||||
}
|
||||
ro := true
|
||||
for _, opt := range entry.Options {
|
||||
if opt == "rw" {
|
||||
ro = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return entry.Mountpoint, entry.FilesystemType, ro
|
||||
}
|
||||
return "", "", true
|
||||
}
|
||||
|
||||
type mtabEntry struct {
|
||||
Partition string
|
||||
Mountpoint string
|
||||
FilesystemType string
|
||||
Options []string
|
||||
}
|
||||
|
||||
func parseMtabEntry(line string) *mtabEntry {
|
||||
// /etc/mtab entries for mounted partitions look like this:
|
||||
// /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
|
||||
if line[0] != '/' {
|
||||
return nil
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
|
||||
if len(fields) < 4 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We do some special parsing of the mountpoint, which may contain space,
|
||||
// tab and newline characters, encoded into the mtab entry line using their
|
||||
// octal-to-string representations. From the GNU mtab man pages:
|
||||
//
|
||||
// "Therefore these characters are encoded in the files and the getmntent
|
||||
// function takes care of the decoding while reading the entries back in.
|
||||
// '\040' is used to encode a space character, '\011' to encode a tab
|
||||
// character, '\012' to encode a newline character, and '\\' to encode a
|
||||
// backslash."
|
||||
mp := fields[1]
|
||||
r := strings.NewReplacer(
|
||||
"\\011", "\t", "\\012", "\n", "\\040", " ", "\\\\", "\\",
|
||||
)
|
||||
mp = r.Replace(mp)
|
||||
|
||||
res := &mtabEntry{
|
||||
Partition: fields[0],
|
||||
Mountpoint: mp,
|
||||
FilesystemType: fields[2],
|
||||
}
|
||||
opts := strings.Split(fields[3], ",")
|
||||
res.Options = opts
|
||||
return res
|
||||
}
|
||||
|
||||
// PartitionMountPoint has been deprecated in 0.2. Please use the
|
||||
// Partition.MountPoint attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionMountPoint(part string) string {
|
||||
msg := `
|
||||
The PartitionMountPoint() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.MountPoint attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionMountPoint(part)
|
||||
}
|
||||
|
||||
func (ctx *context) partitionMountPoint(part string) string {
|
||||
mp, _, _ := ctx.partitionInfo(part)
|
||||
return mp
|
||||
}
|
||||
|
||||
// PartitionType has been deprecated in 0.2. Please use the
|
||||
// Partition.Type attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionType(part string) string {
|
||||
msg := `
|
||||
The PartitionType() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.Type attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionType(part)
|
||||
}
|
||||
|
||||
func (ctx *context) partitionType(part string) string {
|
||||
_, pt, _ := ctx.partitionInfo(part)
|
||||
return pt
|
||||
}
|
||||
|
||||
// PartitionIsReadOnly has been deprecated in 0.2. Please use the
|
||||
// Partition.IsReadOnly attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionIsReadOnly(part string) bool {
|
||||
msg := `
|
||||
The PartitionIsReadOnly() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.IsReadOnly attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionIsReadOnly(part)
|
||||
}
|
||||
|
||||
func (ctx *context) partitionIsReadOnly(part string) bool {
|
||||
_, _, ro := ctx.partitionInfo(part)
|
||||
return ro
|
||||
}
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
// +build !linux,!darwin,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) blockFillInfo(info *BlockInfo) error {
|
||||
return errors.New("blockFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
|
||||
// DiskPhysicalBlockSizeBytes has been deprecated in 0.2. Please use the
|
||||
// Disk.PhysicalBlockSizeBytes attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskPhysicalBlockSizeBytes(disk string) uint64 {
|
||||
msg := `
|
||||
The DiskPhysicalBlockSizeBytes() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.PhysicalBlockSizeBytes
|
||||
attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskPhysicalBlockSizeBytes(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskPhysicalBlockSizeBytes(disk string) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// DiskSizeBytes has been deprecated in 0.2. Please use the Disk.SizeBytes
|
||||
// attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskSizeBytes(disk string) uint64 {
|
||||
msg := `
|
||||
The DiskSizeBytes() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.SizeBytes attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskSizeBytes(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskSizeBytes(disk string) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// DiskNUMANodeID has been deprecated in 0.2. Please use the Disk.NUMANodeID
|
||||
// attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskNUMANodeID(disk string) int {
|
||||
msg := `
|
||||
The DiskNUMANodeID() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.NUMANodeID attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskNUMANodeID(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskNUMANodeID(disk string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// DiskVendor has been deprecated in 0.2. Please use the Disk.Vendor attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskVendor(disk string) string {
|
||||
msg := `
|
||||
The DiskVendor() function has been DEPRECATED and will be
|
||||
removed in the 1.0 release of ghw. Please use the Disk.Vendor attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskVendor(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskVendor(disk string) string {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskModel has been deprecated in 0.2. Please use the Disk.Model attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskModel(disk string) string {
|
||||
msg := `
|
||||
The DiskModel() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the Disk.Model attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskModel(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskModel(disk string) string {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskSerialNumber has been deprecated in 0.2. Please use the Disk.SerialNumber attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskSerialNumber(disk string) string {
|
||||
msg := `
|
||||
The DiskSerialNumber() function has been DEPRECATED and will be removed in the
|
||||
1.0 release of ghw. Please use the Disk.SerialNumber attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskSerialNumber(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskSerialNumber(disk string) string {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskBusPath has been deprecated in 0.2. Please use the Disk.BusPath attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskBusPath(disk string) string {
|
||||
msg := `
|
||||
The DiskBusPath() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the Disk.BusPath attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskBusPath(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskBusPath(disk string) string {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskWWN has been deprecated in 0.2. Please use the Disk.WWN attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskWWN(disk string) string {
|
||||
msg := `
|
||||
The DiskWWN() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the Disk.WWN attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskWWN(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskWWN(disk string) string {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// DiskPartitions has been deprecated in 0.2. Please use the Disk.Partitions attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func DiskPartitions(disk string) []*Partition {
|
||||
msg := `
|
||||
The DiskPartitions() function has been DEPRECATED and will be removed in the
|
||||
1.0 release of ghw. Please use the Disk.Partitions attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.diskPartitions(disk)
|
||||
}
|
||||
|
||||
func (ctx *context) diskPartitions(disk string) []*Partition {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disks has been deprecated in 0.2. Please use the BlockInfo.Disks attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func Disks() []*Disk {
|
||||
msg := `
|
||||
The Disks() function has been DEPRECATED and will be removed in the
|
||||
1.0 release of ghw. Please use the BlockInfo.Disks attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.disks()
|
||||
}
|
||||
|
||||
func (ctx *context) disks() []*Disk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PartitionSizeBytes has been deprecated in 0.2. Please use the
|
||||
// Partition.SizeBytes attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionSizeBytes(part string) uint64 {
|
||||
msg := `
|
||||
The PartitionSizeBytes() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.SizeBytes attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionSizeBytes(part)
|
||||
}
|
||||
|
||||
func (ctx *context) partitionSizeBytes(part string) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// PartitionInfo has been deprecated in 0.2. Please use the Partition struct.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionInfo(part string) (string, string, bool) {
|
||||
msg := `
|
||||
The PartitionInfo() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition struct.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionInfo(part)
|
||||
}
|
||||
|
||||
// Given a full or short partition name, returns the mount point, the type of
|
||||
// the partition and whether it's readonly
|
||||
func (ctx *context) partitionInfo(part string) (string, string, bool) {
|
||||
// full name, short name, read-only
|
||||
return "", "", true
|
||||
}
|
||||
|
||||
// PartitionMountPoint has been deprecated in 0.2. Please use the
|
||||
// Partition.MountPoint attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionMountPoint(part string) string {
|
||||
msg := `
|
||||
The PartitionMountPoint() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.MountPoint attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionMountPoint(part)
|
||||
}
|
||||
|
||||
func (ctx *context) partitionMountPoint(part string) string {
|
||||
mp, _, _ := ctx.partitionInfo(part)
|
||||
return mp
|
||||
}
|
||||
|
||||
// PartitionType has been deprecated in 0.2. Please use the
|
||||
// Partition.Type attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionType(part string) string {
|
||||
msg := `
|
||||
The PartitionType() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.Type attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionType(part)
|
||||
}
|
||||
|
||||
func (ctx *context) partitionType(part string) string {
|
||||
_, pt, _ := ctx.partitionInfo(part)
|
||||
return pt
|
||||
}
|
||||
|
||||
// PartitionIsReadOnly has been deprecated in 0.2. Please use the
|
||||
// Partition.IsReadOnly attribute. TODO(jaypipes): Remove in 1.0.
|
||||
func PartitionIsReadOnly(part string) bool {
|
||||
msg := `
|
||||
The PartitionIsReadOnly() function has been DEPRECATED and will be removed in
|
||||
the 1.0 release of ghw. Please use the Partition.IsReadOnly attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.partitionIsReadOnly(part)
|
||||
}
|
||||
|
||||
func (ctx *context) partitionIsReadOnly(part string) bool {
|
||||
_, _, ro := ctx.partitionInfo(part)
|
||||
return ro
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
// 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"
|
||||
|
||||
"github.com/StackExchange/wmi"
|
||||
)
|
||||
|
||||
const wqlDiskDrive = "SELECT Caption, CreationClassName, DefaultBlockSize, Description, DeviceID, Index, InterfaceType, Manufacturer, MediaType, Model, Name, Partitions, SerialNumber, Size, TotalCylinders, TotalHeads, TotalSectors, TotalTracks, TracksPerCylinder FROM Win32_DiskDrive"
|
||||
|
||||
type win32DiskDrive struct {
|
||||
Caption *string
|
||||
CreationClassName *string
|
||||
DefaultBlockSize *uint64
|
||||
Description *string
|
||||
DeviceID *string
|
||||
Index *uint32 // Used to link with partition
|
||||
InterfaceType *string
|
||||
Manufacturer *string
|
||||
MediaType *string
|
||||
Model *string
|
||||
Name *string
|
||||
Partitions *int32
|
||||
SerialNumber *string
|
||||
Size *uint64
|
||||
TotalCylinders *int64
|
||||
TotalHeads *int32
|
||||
TotalSectors *int64
|
||||
TotalTracks *int64
|
||||
TracksPerCylinder *int32
|
||||
}
|
||||
|
||||
const wqlDiskPartition = "SELECT Access, BlockSize, Caption, CreationClassName, Description, DeviceID, DiskIndex, Index, Name, Size, SystemName, Type FROM Win32_DiskPartition"
|
||||
|
||||
type win32DiskPartition struct {
|
||||
Access *uint16
|
||||
BlockSize *uint64
|
||||
Caption *string
|
||||
CreationClassName *string
|
||||
Description *string
|
||||
DeviceID *string
|
||||
DiskIndex *uint32 // Used to link with Disk Drive
|
||||
Index *uint32
|
||||
Name *string
|
||||
Size *int64
|
||||
SystemName *string
|
||||
Type *string
|
||||
}
|
||||
|
||||
const wqlLogicalDiskToPartition = "SELECT Antecedent, Dependent FROM Win32_LogicalDiskToPartition"
|
||||
|
||||
type win32LogicalDiskToPartition struct {
|
||||
Antecedent *string
|
||||
Dependent *string
|
||||
}
|
||||
|
||||
const wqlLogicalDisk = "SELECT Caption, CreationClassName, Description, DeviceID, FileSystem, FreeSpace, Name, Size, SystemName FROM Win32_LogicalDisk"
|
||||
|
||||
type win32LogicalDisk struct {
|
||||
Caption *string
|
||||
CreationClassName *string
|
||||
Description *string
|
||||
DeviceID *string
|
||||
FileSystem *string
|
||||
FreeSpace *uint64
|
||||
Name *string
|
||||
Size *uint64
|
||||
SystemName *string
|
||||
}
|
||||
|
||||
func (ctx *context) blockFillInfo(info *BlockInfo) error {
|
||||
win32DiskDriveDescriptions, err := getDiskDrives()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
win32DiskPartitionDescriptions, err := getDiskPartitions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
win32LogicalDiskToPartitionDescriptions, err := getLogicalDisksToPartitions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
win32LogicalDiskDescriptions, err := getLogicalDisks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Converting into standard structures
|
||||
disks := make([]*Disk, 0)
|
||||
for _, diskdrive := range win32DiskDriveDescriptions {
|
||||
disk := &Disk{
|
||||
Name: strings.TrimSpace(*diskdrive.DeviceID),
|
||||
SizeBytes: *diskdrive.Size,
|
||||
PhysicalBlockSizeBytes: *diskdrive.DefaultBlockSize,
|
||||
DriveType: toDriveType(*diskdrive.MediaType, *diskdrive.Caption),
|
||||
StorageController: toStorageController(*diskdrive.InterfaceType),
|
||||
BusType: toBusType(*diskdrive.InterfaceType),
|
||||
BusPath: UNKNOWN, // TODO: add information
|
||||
NUMANodeID: -1,
|
||||
Vendor: strings.TrimSpace(*diskdrive.Manufacturer),
|
||||
Model: strings.TrimSpace(*diskdrive.Caption),
|
||||
SerialNumber: strings.TrimSpace(*diskdrive.SerialNumber),
|
||||
WWN: UNKNOWN, // TODO: add information
|
||||
Partitions: make([]*Partition, 0),
|
||||
}
|
||||
for _, diskpartition := range win32DiskPartitionDescriptions {
|
||||
// Finding disk partition linked to current disk drive
|
||||
if diskdrive.Index == diskpartition.DiskIndex {
|
||||
disk.PhysicalBlockSizeBytes = *diskpartition.BlockSize
|
||||
// Finding logical partition linked to current disk partition
|
||||
for _, logicaldisk := range win32LogicalDiskDescriptions {
|
||||
for _, logicaldisktodiskpartition := range win32LogicalDiskToPartitionDescriptions {
|
||||
var desiredAntecedent = "\\\\" + *diskpartition.SystemName + "\\root\\cimv2:" + *diskpartition.CreationClassName + ".DeviceID=\"" + *diskpartition.DeviceID + "\""
|
||||
var desiredDependent = "\\\\" + *logicaldisk.SystemName + "\\root\\cimv2:" + *logicaldisk.CreationClassName + ".DeviceID=\"" + *logicaldisk.DeviceID + "\""
|
||||
if *logicaldisktodiskpartition.Antecedent == desiredAntecedent && *logicaldisktodiskpartition.Dependent == desiredDependent {
|
||||
// Appending Partition
|
||||
p := &Partition{
|
||||
Name: strings.TrimSpace(*logicaldisk.Caption),
|
||||
Label: strings.TrimSpace(*logicaldisk.Caption),
|
||||
SizeBytes: *logicaldisk.Size,
|
||||
MountPoint: *logicaldisk.DeviceID,
|
||||
Type: *diskpartition.Type,
|
||||
IsReadOnly: toReadOnly(*diskpartition.Access),
|
||||
}
|
||||
disk.Partitions = append(disk.Partitions, p)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
disks = append(disks, disk)
|
||||
}
|
||||
|
||||
info.Disks = disks
|
||||
var tpb uint64
|
||||
for _, d := range info.Disks {
|
||||
tpb += d.SizeBytes
|
||||
}
|
||||
info.TotalPhysicalBytes = tpb
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDiskDrives() ([]win32DiskDrive, error) {
|
||||
// Getting disks drives data from WMI
|
||||
var win3232DiskDriveDescriptions []win32DiskDrive
|
||||
if err := wmi.Query(wqlDiskDrive, &win3232DiskDriveDescriptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return win3232DiskDriveDescriptions, nil
|
||||
}
|
||||
|
||||
func getDiskPartitions() ([]win32DiskPartition, error) {
|
||||
// Getting disk partitions from WMI
|
||||
var win32DiskPartitionDescriptions []win32DiskPartition
|
||||
if err := wmi.Query(wqlDiskPartition, &win32DiskPartitionDescriptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return win32DiskPartitionDescriptions, nil
|
||||
}
|
||||
|
||||
func getLogicalDisksToPartitions() ([]win32LogicalDiskToPartition, error) {
|
||||
// Getting links between logical disks and partitions from WMI
|
||||
var win32LogicalDiskToPartitionDescriptions []win32LogicalDiskToPartition
|
||||
if err := wmi.Query(wqlLogicalDiskToPartition, &win32LogicalDiskToPartitionDescriptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return win32LogicalDiskToPartitionDescriptions, nil
|
||||
}
|
||||
|
||||
func getLogicalDisks() ([]win32LogicalDisk, error) {
|
||||
// Getting logical disks from WMI
|
||||
var win32LogicalDiskDescriptions []win32LogicalDisk
|
||||
if err := wmi.Query(wqlLogicalDisk, &win32LogicalDiskDescriptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return win32LogicalDiskDescriptions, nil
|
||||
}
|
||||
|
||||
func toDriveType(mediaType string, caption string) DriveType {
|
||||
mediaType = strings.ToLower(mediaType)
|
||||
caption = strings.ToLower(caption)
|
||||
if strings.Contains(mediaType, "fixed") || strings.Contains(mediaType, "ssd") || strings.Contains(caption, "ssd") {
|
||||
return DRIVE_TYPE_SSD
|
||||
} else if strings.ContainsAny(mediaType, "hdd") {
|
||||
return DRIVE_TYPE_HDD
|
||||
}
|
||||
return DRIVE_TYPE_UNKNOWN
|
||||
}
|
||||
|
||||
// TODO: improve
|
||||
func toStorageController(interfaceType string) StorageController {
|
||||
var storageController StorageController
|
||||
switch interfaceType {
|
||||
case "SCSI":
|
||||
storageController = STORAGE_CONTROLLER_SCSI
|
||||
case "IDE":
|
||||
storageController = STORAGE_CONTROLLER_IDE
|
||||
default:
|
||||
storageController = STORAGE_CONTROLLER_UNKNOWN
|
||||
}
|
||||
return storageController
|
||||
}
|
||||
|
||||
// TODO: improve
|
||||
func toBusType(interfaceType string) BusType {
|
||||
var busType BusType
|
||||
switch interfaceType {
|
||||
case "SCSI":
|
||||
busType = BUS_TYPE_SCSI
|
||||
case "IDE":
|
||||
busType = BUS_TYPE_IDE
|
||||
default:
|
||||
busType = BUS_TYPE_UNKNOWN
|
||||
}
|
||||
return busType
|
||||
}
|
||||
|
||||
// TODO: improve
|
||||
func toReadOnly(access uint16) bool {
|
||||
// See Access property from: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskpartition
|
||||
return access == 0x1
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// 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"
|
||||
|
||||
type BusType int
|
||||
|
||||
const (
|
||||
BUS_TYPE_UNKNOWN BusType = iota
|
||||
BUS_TYPE_IDE
|
||||
BUS_TYPE_PCI
|
||||
BUS_TYPE_SCSI
|
||||
BUS_TYPE_NVME
|
||||
BUS_TYPE_VIRTIO
|
||||
)
|
||||
|
||||
var (
|
||||
busTypeString = map[BusType]string{
|
||||
BUS_TYPE_UNKNOWN: "Unknown",
|
||||
BUS_TYPE_IDE: "IDE",
|
||||
BUS_TYPE_PCI: "PCI",
|
||||
BUS_TYPE_SCSI: "SCSI",
|
||||
BUS_TYPE_NVME: "NVMe",
|
||||
BUS_TYPE_VIRTIO: "Virtio",
|
||||
}
|
||||
)
|
||||
|
||||
func (bt BusType) String() string {
|
||||
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
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// 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 "fmt"
|
||||
|
||||
var (
|
||||
chassisTypeDescriptions = map[string]string{
|
||||
"1": "Other",
|
||||
"2": "Unknown",
|
||||
"3": "Desktop",
|
||||
"4": "Low profile desktop",
|
||||
"5": "Pizza box",
|
||||
"6": "Mini tower",
|
||||
"7": "Tower",
|
||||
"8": "Portable",
|
||||
"9": "Laptop",
|
||||
"10": "Notebook",
|
||||
"11": "Hand held",
|
||||
"12": "Docking station",
|
||||
"13": "All in one",
|
||||
"14": "Sub notebook",
|
||||
"15": "Space-saving",
|
||||
"16": "Lunch box",
|
||||
"17": "Main server chassis",
|
||||
"18": "Expansion chassis",
|
||||
"19": "SubChassis",
|
||||
"20": "Bus Expansion chassis",
|
||||
"21": "Peripheral chassis",
|
||||
"22": "RAID chassis",
|
||||
"23": "Rack mount chassis",
|
||||
"24": "Sealed-case PC",
|
||||
"25": "Multi-system chassis",
|
||||
"26": "Compact PCI",
|
||||
"27": "Advanced TCA",
|
||||
"28": "Blade",
|
||||
"29": "Blade enclosure",
|
||||
"30": "Tablet",
|
||||
"31": "Convertible",
|
||||
"32": "Detachable",
|
||||
"33": "IoT gateway",
|
||||
"34": "Embedded PC",
|
||||
"35": "Mini PC",
|
||||
"36": "Stick PC",
|
||||
}
|
||||
)
|
||||
|
||||
// ChassisInfo defines chassis release information
|
||||
type ChassisInfo struct {
|
||||
AssetTag string `json:"asset_tag"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Type string `json:"type"`
|
||||
TypeDescription string `json:"type_description"`
|
||||
Vendor string `json:"vendor"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (i *ChassisInfo) String() string {
|
||||
|
||||
vendorStr := ""
|
||||
if i.Vendor != "" {
|
||||
vendorStr = " vendor=" + i.Vendor
|
||||
}
|
||||
serialStr := ""
|
||||
if i.SerialNumber != "" && i.SerialNumber != UNKNOWN {
|
||||
serialStr = " serial=" + i.SerialNumber
|
||||
}
|
||||
versionStr := ""
|
||||
if i.Version != "" {
|
||||
versionStr = " version=" + i.Version
|
||||
}
|
||||
|
||||
res := fmt.Sprintf(
|
||||
"chassis type=%s%s%s%s",
|
||||
i.TypeDescription,
|
||||
vendorStr,
|
||||
serialStr,
|
||||
versionStr,
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// Chassis returns a pointer to a ChassisInfo struct containing information
|
||||
// about the host's chassis
|
||||
func Chassis(opts ...*WithOption) (*ChassisInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &ChassisInfo{}
|
||||
if err := ctx.chassisFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate chassis information in a top-level
|
||||
// "chassis" YAML/JSON map/object key
|
||||
type chassisPrinter struct {
|
||||
Info *ChassisInfo `json:"chassis"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the chassis information formatted as YAML
|
||||
// under a top-level "dmi:" key
|
||||
func (info *ChassisInfo) YAMLString() string {
|
||||
return safeYAML(chassisPrinter{info})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the chassis information formatted as JSON
|
||||
// under a top-level "chassis:" key
|
||||
func (info *ChassisInfo) JSONString(indent bool) string {
|
||||
return safeJSON(chassisPrinter{info}, indent)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package ghw
|
||||
|
||||
func (ctx *context) chassisFillInfo(info *ChassisInfo) error {
|
||||
info.AssetTag = ctx.dmiItem("chassis_asset_tag")
|
||||
info.SerialNumber = ctx.dmiItem("chassis_serial")
|
||||
info.Type = ctx.dmiItem("chassis_type")
|
||||
typeDesc, found := chassisTypeDescriptions[info.Type]
|
||||
if !found {
|
||||
typeDesc = UNKNOWN
|
||||
}
|
||||
info.TypeDescription = typeDesc
|
||||
info.Vendor = ctx.dmiItem("chassis_vendor")
|
||||
info.Version = ctx.dmiItem("chassis_version")
|
||||
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) chassisFillInfo(info *ChassisInfo) error {
|
||||
return errors.New("chassisFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// 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 (
|
||||
"github.com/StackExchange/wmi"
|
||||
)
|
||||
|
||||
const wqlChassis = "SELECT Caption, Description, Name, Manufacturer, Model, SerialNumber, Tag, TypeDescriptions, Version FROM CIM_Chassis"
|
||||
|
||||
type win32Chassis struct {
|
||||
Caption *string
|
||||
Description *string
|
||||
Name *string
|
||||
Manufacturer *string
|
||||
Model *string
|
||||
SerialNumber *string
|
||||
Tag *string
|
||||
TypeDescriptions []string
|
||||
Version *string
|
||||
}
|
||||
|
||||
func (ctx *context) chassisFillInfo(info *ChassisInfo) error {
|
||||
// Getting data from WMI
|
||||
var win32ChassisDescriptions []win32Chassis
|
||||
if err := wmi.Query(wqlChassis, &win32ChassisDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(win32ChassisDescriptions) > 0 {
|
||||
info.AssetTag = *win32ChassisDescriptions[0].Tag
|
||||
info.SerialNumber = *win32ChassisDescriptions[0].SerialNumber
|
||||
info.Type = UNKNOWN // TODO:
|
||||
info.TypeDescription = *win32ChassisDescriptions[0].Model
|
||||
info.Vendor = *win32ChassisDescriptions[0].Manufacturer
|
||||
info.Version = *win32ChassisDescriptions[0].Version
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ProcessorCore describes a physical host processor core. A processor core is
|
||||
// a separate processing unit within some types of central processing units
|
||||
// (CPU).
|
||||
type ProcessorCore struct {
|
||||
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
|
||||
Id int `json:"-"`
|
||||
// ID is the `uint32` identifier that the host gave this core. Note that
|
||||
// this does *not* necessarily equate to a zero-based index of the core
|
||||
// within a physical package. For example, the core IDs for an Intel Core
|
||||
// i7 are 0, 1, 2, 8, 9, and 10
|
||||
ID int `json:"id"`
|
||||
// Index is the zero-based index of the core on the physical processor
|
||||
// package
|
||||
Index int `json:"index"`
|
||||
// NumThreads is the number of hardware threads associated with the core
|
||||
NumThreads uint32 `json:"total_threads"`
|
||||
// LogicalProcessors is a slice of ints representing the logical processor
|
||||
// IDs assigned to any processing unit for the core
|
||||
LogicalProcessors []int `json:"logical_processors"`
|
||||
}
|
||||
|
||||
// String returns a short string indicating important information about the
|
||||
// processor core
|
||||
func (c *ProcessorCore) String() string {
|
||||
return fmt.Sprintf(
|
||||
"processor core #%d (%d threads), logical processors %v",
|
||||
c.Index,
|
||||
c.NumThreads,
|
||||
c.LogicalProcessors,
|
||||
)
|
||||
}
|
||||
|
||||
// Processor describes a physical host central processing unit (CPU).
|
||||
type Processor struct {
|
||||
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
|
||||
Id int `json:"-"`
|
||||
// ID is the physical processor `uint32` ID according to the system
|
||||
ID int `json:"id"`
|
||||
// NumCores is the number of physical cores in the processor package
|
||||
NumCores uint32 `json:"total_cores"`
|
||||
// NumThreads is the number of hardware threads in the processor package
|
||||
NumThreads uint32 `json:"total_threads"`
|
||||
// Vendor is a string containing the vendor name
|
||||
Vendor string `json:"vendor"`
|
||||
// Model` is a string containing the vendor's model name
|
||||
Model string `json:"model"`
|
||||
// Capabilities is a slice of strings indicating the features the processor
|
||||
// has enabled
|
||||
Capabilities []string `json:"capabilities"`
|
||||
// Cores is a slice of ProcessorCore` struct pointers that are packed onto
|
||||
// this physical processor
|
||||
Cores []*ProcessorCore `json:"cores"`
|
||||
}
|
||||
|
||||
// HasCapability returns true if the Processor has the supplied cpuid
|
||||
// capability, false otherwise. Example of cpuid capabilities would be 'vmx' or
|
||||
// 'sse4_2'. To see a list of potential cpuid capabilitiies, see the section on
|
||||
// CPUID feature bits in the following article:
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/CPUID
|
||||
func (p *Processor) HasCapability(find string) bool {
|
||||
for _, c := range p.Capabilities {
|
||||
if c == find {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String returns a short string describing the Processor
|
||||
func (p *Processor) String() string {
|
||||
ncs := "cores"
|
||||
if p.NumCores == 1 {
|
||||
ncs = "core"
|
||||
}
|
||||
nts := "threads"
|
||||
if p.NumThreads == 1 {
|
||||
nts = "thread"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"physical package #%d (%d %s, %d hardware %s)",
|
||||
p.ID,
|
||||
p.NumCores,
|
||||
ncs,
|
||||
p.NumThreads,
|
||||
nts,
|
||||
)
|
||||
}
|
||||
|
||||
// CPUInfo describes all central processing unit (CPU) functionality on a host.
|
||||
// Returned by the `ghw.CPU()` function.
|
||||
type CPUInfo struct {
|
||||
// TotalCores is the total number of physical cores the host system
|
||||
// contains
|
||||
TotalCores uint32 `json:"total_cores"`
|
||||
// TotalThreads is the total number of hardware threads the host system
|
||||
// contains
|
||||
TotalThreads uint32 `json:"total_threads"`
|
||||
// Processors is a slice of Processor struct pointers, one for each
|
||||
// physical processor package contained in the host
|
||||
Processors []*Processor `json:"processors"`
|
||||
}
|
||||
|
||||
// CPU returns a CPUInfo struct that contains information about the CPUs on the
|
||||
// host system
|
||||
func CPU(opts ...*WithOption) (*CPUInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &CPUInfo{}
|
||||
if err := ctx.cpuFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// String returns a short string indicating a summary of CPU information
|
||||
func (i *CPUInfo) String() string {
|
||||
nps := "packages"
|
||||
if len(i.Processors) == 1 {
|
||||
nps = "package"
|
||||
}
|
||||
ncs := "cores"
|
||||
if i.TotalCores == 1 {
|
||||
ncs = "core"
|
||||
}
|
||||
nts := "threads"
|
||||
if i.TotalThreads == 1 {
|
||||
nts = "thread"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"cpu (%d physical %s, %d %s, %d hardware %s)",
|
||||
len(i.Processors),
|
||||
nps,
|
||||
i.TotalCores,
|
||||
ncs,
|
||||
i.TotalThreads,
|
||||
nts,
|
||||
)
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate cpu information in a top-level
|
||||
// "cpu" YAML/JSON map/object key
|
||||
type cpuPrinter struct {
|
||||
Info *CPUInfo `json:"cpu"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the cpu information formatted as YAML
|
||||
// under a top-level "cpu:" key
|
||||
func (i *CPUInfo) YAMLString() string {
|
||||
return safeYAML(cpuPrinter{i})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the cpu information formatted as JSON
|
||||
// under a top-level "cpu:" key
|
||||
func (i *CPUInfo) JSONString(indent bool) string {
|
||||
return safeJSON(cpuPrinter{i}, indent)
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (ctx *context) cpuFillInfo(info *CPUInfo) error {
|
||||
info.Processors = ctx.processorsGet()
|
||||
var totCores uint32
|
||||
var totThreads uint32
|
||||
for _, p := range info.Processors {
|
||||
totCores += p.NumCores
|
||||
totThreads += p.NumThreads
|
||||
}
|
||||
info.TotalCores = totCores
|
||||
info.TotalThreads = totThreads
|
||||
return nil
|
||||
}
|
||||
|
||||
// Processors has been DEPRECATED in 0.2 and will be REMOVED in 1.0. Please use
|
||||
// the CPUInfo.Processors attribute.
|
||||
// TODO(jaypipes): Remove in 1.0
|
||||
func Processors() []*Processor {
|
||||
ctx := contextFromEnv()
|
||||
return ctx.processorsGet()
|
||||
}
|
||||
|
||||
func (ctx *context) processorsGet() []*Processor {
|
||||
procs := make([]*Processor, 0)
|
||||
|
||||
r, err := os.Open(ctx.pathProcCpuinfo())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer safeClose(r)
|
||||
|
||||
// An array of maps of attributes describing the logical processor
|
||||
procAttrs := make([]map[string]string, 0)
|
||||
curProcAttrs := make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
// Output of /proc/cpuinfo has a blank newline to separate logical
|
||||
// processors, so here we collect up all the attributes we've
|
||||
// collected for this logical processor block
|
||||
procAttrs = append(procAttrs, curProcAttrs)
|
||||
// Reset the current set of processor attributes...
|
||||
curProcAttrs = make(map[string]string)
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(line, ":")
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
curProcAttrs[key] = value
|
||||
}
|
||||
|
||||
// Build a set of physical processor IDs which represent the physical
|
||||
// package of the CPU
|
||||
setPhysicalIDs := make(map[int]bool)
|
||||
for _, attrs := range procAttrs {
|
||||
pid, err := strconv.Atoi(attrs["physical id"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
setPhysicalIDs[pid] = true
|
||||
}
|
||||
|
||||
for pid := range setPhysicalIDs {
|
||||
p := &Processor{
|
||||
Id: pid,
|
||||
}
|
||||
// The indexes into the array of attribute maps for each logical
|
||||
// processor within the physical processor
|
||||
lps := make([]int, 0)
|
||||
for x := range procAttrs {
|
||||
lppid, err := strconv.Atoi(procAttrs[x]["physical id"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if pid == lppid {
|
||||
lps = append(lps, x)
|
||||
}
|
||||
}
|
||||
first := procAttrs[lps[0]]
|
||||
p.Model = first["model name"]
|
||||
p.Vendor = first["vendor_id"]
|
||||
numCores, err := strconv.Atoi(first["cpu cores"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.NumCores = uint32(numCores)
|
||||
numThreads, err := strconv.Atoi(first["siblings"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.NumThreads = uint32(numThreads)
|
||||
|
||||
// The flags field is a space-separated list of CPU capabilities
|
||||
p.Capabilities = strings.Split(first["flags"], " ")
|
||||
|
||||
cores := make([]*ProcessorCore, 0)
|
||||
for _, lpidx := range lps {
|
||||
lpid, err := strconv.Atoi(procAttrs[lpidx]["processor"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
coreID, err := strconv.Atoi(procAttrs[lpidx]["core id"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var core *ProcessorCore
|
||||
for _, c := range cores {
|
||||
if c.ID == coreID {
|
||||
c.LogicalProcessors = append(
|
||||
c.LogicalProcessors,
|
||||
lpid,
|
||||
)
|
||||
c.NumThreads = uint32(len(c.LogicalProcessors))
|
||||
core = c
|
||||
}
|
||||
}
|
||||
if core == nil {
|
||||
coreLps := make([]int, 1)
|
||||
coreLps[0] = lpid
|
||||
core = &ProcessorCore{
|
||||
ID: coreID,
|
||||
Index: len(cores),
|
||||
NumThreads: 1,
|
||||
LogicalProcessors: coreLps,
|
||||
}
|
||||
cores = append(cores, core)
|
||||
}
|
||||
}
|
||||
p.Cores = cores
|
||||
procs = append(procs, p)
|
||||
}
|
||||
return procs
|
||||
}
|
||||
|
||||
func (ctx *context) coresForNode(nodeID int) ([]*ProcessorCore, error) {
|
||||
// The /sys/devices/system/node/nodeX directory contains a subdirectory
|
||||
// called 'cpuX' for each logical processor assigned to the node. Each of
|
||||
// those subdirectories contains a topology subdirectory which has a
|
||||
// core_id file that indicates the 0-based identifier of the physical core
|
||||
// the logical processor (hardware thread) is on.
|
||||
path := filepath.Join(
|
||||
ctx.pathSysDevicesSystemNode(),
|
||||
fmt.Sprintf("node%d", nodeID),
|
||||
)
|
||||
cores := make([]*ProcessorCore, 0)
|
||||
|
||||
findCoreByID := func(coreID int) *ProcessorCore {
|
||||
for _, c := range cores {
|
||||
if c.Id == coreID {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
c := &ProcessorCore{
|
||||
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
|
||||
Id: coreID,
|
||||
ID: coreID,
|
||||
Index: len(cores),
|
||||
LogicalProcessors: make([]int, 0),
|
||||
}
|
||||
cores = append(cores, c)
|
||||
return c
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !strings.HasPrefix(filename, "cpu") {
|
||||
continue
|
||||
}
|
||||
if filename == "cpumap" || filename == "cpulist" {
|
||||
// There are two files in the node directory that start with 'cpu'
|
||||
// but are not subdirectories ('cpulist' and 'cpumap'). Ignore
|
||||
// these files.
|
||||
continue
|
||||
}
|
||||
// Grab the logical processor ID by cutting the integer from the
|
||||
// /sys/devices/system/node/nodeX/cpuX filename
|
||||
cpuPath := filepath.Join(path, filename)
|
||||
procID, err := strconv.Atoi(filename[3:])
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(
|
||||
os.Stderr,
|
||||
"failed to determine procID from %s. Expected integer after 3rd char.",
|
||||
filename,
|
||||
)
|
||||
continue
|
||||
}
|
||||
coreIDPath := filepath.Join(cpuPath, "topology", "core_id")
|
||||
coreID := safeIntFromFile(coreIDPath)
|
||||
core := findCoreByID(coreID)
|
||||
core.LogicalProcessors = append(
|
||||
core.LogicalProcessors,
|
||||
procID,
|
||||
)
|
||||
}
|
||||
|
||||
for _, c := range cores {
|
||||
c.NumThreads = uint32(len(c.LogicalProcessors))
|
||||
}
|
||||
|
||||
return cores, nil
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) cpuFillInfo(info *CPUInfo) error {
|
||||
return errors.New("cpuFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
|
||||
// Processors has been DEPRECATED in 0.2 and will be REMOVED in 1.0. Please use
|
||||
// the CPUInfo.Processors attribute.
|
||||
// TODO(jaypipes): Remove in 1.0
|
||||
func Processors() []*Processor {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
func (ctx *context) coresForNode(nodeID int) ([]*ProcessorCore, error) {
|
||||
return nil, errors.New("coresForNode not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// +build !linux
|
||||
// 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 (
|
||||
"github.com/StackExchange/wmi"
|
||||
)
|
||||
|
||||
const wmqlProcessor = "SELECT Manufacturer, Name, NumberOfLogicalProcessors, NumberOfCores FROM Win32_Processor"
|
||||
|
||||
type win32Processor struct {
|
||||
Manufacturer *string
|
||||
Name *string
|
||||
NumberOfLogicalProcessors uint32
|
||||
NumberOfCores uint32
|
||||
}
|
||||
|
||||
func (ctx *context) cpuFillInfo(info *CPUInfo) error {
|
||||
// Getting info from WMI
|
||||
var win32descriptions []win32Processor
|
||||
if err := wmi.Query(wmqlProcessor, &win32descriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
// Converting into standard structures
|
||||
info.Processors = ctx.processorsGet(win32descriptions)
|
||||
var totCores uint32
|
||||
var totThreads uint32
|
||||
for _, p := range info.Processors {
|
||||
totCores += p.NumCores
|
||||
totThreads += p.NumThreads
|
||||
}
|
||||
info.TotalCores = totCores
|
||||
info.TotalThreads = totThreads
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *context) processorsGet(win32descriptions []win32Processor) []*Processor {
|
||||
var procs []*Processor
|
||||
// Converting into standard structures
|
||||
for index, description := range win32descriptions {
|
||||
p := &Processor{
|
||||
Id: index, // TODO: how to get a decent "Physical ID" to use ?
|
||||
Model: *description.Name,
|
||||
Vendor: *description.Manufacturer,
|
||||
NumCores: description.NumberOfCores,
|
||||
NumThreads: description.NumberOfLogicalProcessors,
|
||||
}
|
||||
procs = append(procs, p)
|
||||
}
|
||||
return procs
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// 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 (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (ctx *context) dmiItem(value string) string {
|
||||
path := filepath.Join(ctx.pathSysClassDMI(), "id", value)
|
||||
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
warn("Unable to read %s: %s\n", value, err)
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) dmiItem(value string) error {
|
||||
return errors.New("dmiItem not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
//
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
/*
|
||||
package ghw can determine various hardware-related
|
||||
information about the host computer:
|
||||
|
||||
* Memory
|
||||
* CPU
|
||||
* Block storage
|
||||
* Topology
|
||||
* Network
|
||||
* PCI
|
||||
* GPU
|
||||
|
||||
Memory
|
||||
|
||||
Information about the host computer's memory can be retrieved using the
|
||||
Memory function which returns a pointer to a MemoryInfo struct.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
memory, err := ghw.Memory()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting memory info: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println(memory.String())
|
||||
}
|
||||
|
||||
CPU
|
||||
|
||||
The CPU function returns a CPUInfo struct that contains information about
|
||||
the CPUs on the host system.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cpu, err := ghw.CPU()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting CPU info: %v", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Block storage
|
||||
|
||||
Information about the host computer's local block storage is returned from
|
||||
the Block function. This function returns a pointer to a BlockInfo struct.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
block, err := ghw.Block()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting block storage info: %v", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Topology
|
||||
|
||||
Information about the host computer's architecture (NUMA vs. SMP), the
|
||||
host's node layout and processor caches can be retrieved from the Topology
|
||||
function. This function returns a pointer to a TopologyInfo struct.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
topology, err := ghw.Topology()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting topology info: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", topology)
|
||||
|
||||
for _, node := range topology.Nodes {
|
||||
fmt.Printf(" %v\n", node)
|
||||
for _, cache := range node.Caches {
|
||||
fmt.Printf(" %v\n", cache)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Network
|
||||
|
||||
Information about the host computer's networking hardware is returned from
|
||||
the Network function. This function returns a pointer to a NetworkInfo
|
||||
struct.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
net, err := ghw.Network()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting network info: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", net)
|
||||
|
||||
for _, nic := range net.NICs {
|
||||
fmt.Printf(" %v\n", nic)
|
||||
|
||||
enabledCaps := make([]int, 0)
|
||||
for x, cap := range nic.Capabilities {
|
||||
if cap.IsEnabled {
|
||||
enabledCaps = append(enabledCaps, x)
|
||||
}
|
||||
}
|
||||
if len(enabledCaps) > 0 {
|
||||
fmt.Printf(" enabled capabilities:\n")
|
||||
for _, x := range enabledCaps {
|
||||
fmt.Printf(" - %s\n", nic.Capabilities[x].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PCI
|
||||
|
||||
ghw contains a PCI database inspection and querying facility that allows
|
||||
developers to not only gather information about devices on a local PCI bus
|
||||
but also query for information about hardware device classes, vendor and
|
||||
product information.
|
||||
|
||||
**NOTE**: Parsing of the PCI-IDS file database is provided by the separate
|
||||
http://github.com/jaypipes/pcidb library. You can read that library's
|
||||
README for more information about the various structs that are exposed on
|
||||
the PCIInfo struct.
|
||||
|
||||
PCIInfo.ListDevices is used to iterate over a host's PCI devices:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pci, err := ghw.PCI()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting PCI info: %v", err)
|
||||
}
|
||||
fmt.Printf("host PCI devices:\n")
|
||||
fmt.Println("====================================================")
|
||||
devices := pci.ListDevices()
|
||||
if len(devices) == 0 {
|
||||
fmt.Printf("error: could not retrieve PCI devices\n")
|
||||
return
|
||||
}
|
||||
|
||||
for _, device := range devices {
|
||||
vendor := device.Vendor
|
||||
vendorName := vendor.Name
|
||||
if len(vendor.Name) > 20 {
|
||||
vendorName = string([]byte(vendorName)[0:17]) + "..."
|
||||
}
|
||||
product := device.Product
|
||||
productName := product.Name
|
||||
if len(product.Name) > 40 {
|
||||
productName = string([]byte(productName)[0:37]) + "..."
|
||||
}
|
||||
fmt.Printf("%-12s\t%-20s\t%-40s\n", device.Address, vendorName, productName)
|
||||
}
|
||||
}
|
||||
|
||||
The following code snippet shows how to call the PCIInfo.GetDevice method
|
||||
and use its returned PCIDevice struct pointer:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pci, err := ghw.PCI()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting PCI info: %v", err)
|
||||
}
|
||||
|
||||
addr := "0000:00:00.0"
|
||||
if len(os.Args) == 2 {
|
||||
addr = os.Args[1]
|
||||
}
|
||||
fmt.Printf("PCI device information for %s\n", addr)
|
||||
fmt.Println("====================================================")
|
||||
deviceInfo := pci.GetDevice(addr)
|
||||
if deviceInfo == nil {
|
||||
fmt.Printf("could not retrieve PCI device information for %s\n", addr)
|
||||
return
|
||||
}
|
||||
|
||||
vendor := deviceInfo.Vendor
|
||||
fmt.Printf("Vendor: %s [%s]\n", vendor.Name, vendor.ID)
|
||||
product := deviceInfo.Product
|
||||
fmt.Printf("Product: %s [%s]\n", product.Name, product.ID)
|
||||
subsystem := deviceInfo.Subsystem
|
||||
subvendor := pci.Vendors[subsystem.VendorID]
|
||||
subvendorName := "UNKNOWN"
|
||||
if subvendor != nil {
|
||||
subvendorName = subvendor.Name
|
||||
}
|
||||
fmt.Printf("Subsystem: %s [%s] (Subvendor: %s)\n", subsystem.Name, subsystem.ID, subvendorName)
|
||||
class := deviceInfo.Class
|
||||
fmt.Printf("Class: %s [%s]\n", class.Name, class.ID)
|
||||
subclass := deviceInfo.Subclass
|
||||
fmt.Printf("Subclass: %s [%s]\n", subclass.Name, subclass.ID)
|
||||
progIface := deviceInfo.ProgrammingInterface
|
||||
fmt.Printf("Programming Interface: %s [%s]\n", progIface.Name, progIface.ID)
|
||||
}
|
||||
|
||||
GPU
|
||||
|
||||
Information about the host computer's graphics hardware is returned from
|
||||
the GPU function. This function returns a pointer to a GPUInfo struct.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gpu, err := ghw.GPU()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting GPU info: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", gpu)
|
||||
|
||||
for _, card := range gpu.GraphicsCards {
|
||||
fmt.Printf(" %v\n", card)
|
||||
}
|
||||
}
|
||||
*/
|
||||
package ghw
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
module github.com/jaypipes/ghw
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jaypipes/pcidb v0.5.0
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb
|
||||
)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jaypipes/pcidb v0.5.0 h1:4W5gZ+G7QxydevI8/MmmKdnIPJpURqJ2JNXTzfLxF5c=
|
||||
github.com/jaypipes/pcidb v0.5.0/go.mod h1:L2RGk04sfRhp5wvHO0gfRAMoLY/F3PKv/nwJeVoho0o=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type GraphicsCard struct {
|
||||
// the PCI address where the graphics card can be found
|
||||
Address string `json:"address"`
|
||||
// The "index" of the card on the bus (generally not useful information,
|
||||
// but might as well include it)
|
||||
Index int `json:"index"`
|
||||
// pointer to a PCIDevice struct that describes the vendor and product
|
||||
// model, etc
|
||||
// TODO(jaypipes): Rename this field to PCI, instead of DeviceInfo
|
||||
DeviceInfo *PCIDevice `json:"pci"`
|
||||
// Topology node that the graphics card is affined to. Will be nil if the
|
||||
// architecture is not NUMA.
|
||||
Node *TopologyNode `json:"node,omitempty"`
|
||||
}
|
||||
|
||||
func (card *GraphicsCard) String() string {
|
||||
deviceStr := card.Address
|
||||
if card.DeviceInfo != nil {
|
||||
deviceStr = card.DeviceInfo.String()
|
||||
}
|
||||
nodeStr := ""
|
||||
if card.Node != nil {
|
||||
nodeStr = fmt.Sprintf(" [affined to NUMA node %d]", card.Node.Id)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"card #%d %s@%s",
|
||||
card.Index,
|
||||
nodeStr,
|
||||
deviceStr,
|
||||
)
|
||||
}
|
||||
|
||||
type GPUInfo struct {
|
||||
GraphicsCards []*GraphicsCard `json:"cards"`
|
||||
}
|
||||
|
||||
func GPU(opts ...*WithOption) (*GPUInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &GPUInfo{}
|
||||
if err := ctx.gpuFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (i *GPUInfo) String() string {
|
||||
numCardsStr := "cards"
|
||||
if len(i.GraphicsCards) == 1 {
|
||||
numCardsStr = "card"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"gpu (%d graphics %s)",
|
||||
len(i.GraphicsCards),
|
||||
numCardsStr,
|
||||
)
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate gpu information in a top-level
|
||||
// "gpu" YAML/JSON map/object key
|
||||
type gpuPrinter struct {
|
||||
Info *GPUInfo `json:"gpu"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the gpu information formatted as YAML
|
||||
// under a top-level "gpu:" key
|
||||
func (i *GPUInfo) YAMLString() string {
|
||||
return safeYAML(gpuPrinter{i})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the gpu information formatted as JSON
|
||||
// under a top-level "gpu:" key
|
||||
func (i *GPUInfo) JSONString(indent bool) string {
|
||||
return safeJSON(gpuPrinter{i}, indent)
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
// 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
_WARN_NO_SYS_CLASS_DRM = `
|
||||
/sys/class/drm does not exist on this system (likely the host system is a
|
||||
virtual machine or container with no graphics). Therefore,
|
||||
GPUInfo.GraphicsCards will be an empty array.
|
||||
`
|
||||
)
|
||||
|
||||
func (ctx *context) gpuFillInfo(info *GPUInfo) error {
|
||||
// In Linux, each graphics card is listed under the /sys/class/drm
|
||||
// directory as a symbolic link named "cardN", where N is a zero-based
|
||||
// index of the card in the system. "DRM" stands for Direct Rendering
|
||||
// Manager and is the Linux subsystem that is responsible for graphics I/O
|
||||
//
|
||||
// Each card may have multiple symbolic
|
||||
// links in this directory representing the interfaces from the graphics
|
||||
// card over a particular wire protocol (HDMI, DisplayPort, etc). These
|
||||
// symbolic links are named cardN-<INTERFACE_TYPE>-<DISPLAY_ID>. For
|
||||
// instance, on one of my local workstations with an NVIDIA GTX 1050ti
|
||||
// graphics card with one HDMI, one DisplayPort, and one DVI interface to
|
||||
// the card, I see the following in /sys/class/drm:
|
||||
//
|
||||
// $ ll /sys/class/drm/
|
||||
// total 0
|
||||
// drwxr-xr-x 2 root root 0 Jul 16 11:50 ./
|
||||
// drwxr-xr-x 75 root root 0 Jul 16 11:50 ../
|
||||
// lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/
|
||||
// lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0-DP-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-DP-1/
|
||||
// lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0-DVI-D-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-DVI-D-1/
|
||||
// lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0-HDMI-A-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-HDMI-A-1/
|
||||
//
|
||||
// In this routine, we are only interested in the first link (card0), which
|
||||
// we follow to gather information about the actual device from the PCI
|
||||
// subsystem (we query the modalias file of the PCI device's sysfs
|
||||
// directory using the `ghw.PCIInfo.GetDevice()` function.
|
||||
links, err := ioutil.ReadDir(ctx.pathSysClassDrm())
|
||||
if err != nil {
|
||||
warn(_WARN_NO_SYS_CLASS_DRM)
|
||||
return nil
|
||||
}
|
||||
cards := make([]*GraphicsCard, 0)
|
||||
for _, link := range links {
|
||||
lname := link.Name()
|
||||
if !strings.HasPrefix(lname, "card") {
|
||||
continue
|
||||
}
|
||||
if strings.ContainsRune(lname, '-') {
|
||||
continue
|
||||
}
|
||||
// Grab the card's zero-based integer index
|
||||
lnameBytes := []byte(lname)
|
||||
cardIdx, err := strconv.Atoi(string(lnameBytes[4:]))
|
||||
if err != nil {
|
||||
cardIdx = -1
|
||||
}
|
||||
|
||||
// Calculate the card's PCI address by looking at the symbolic link's
|
||||
// target
|
||||
lpath := filepath.Join(ctx.pathSysClassDrm(), lname)
|
||||
dest, err := os.Readlink(lpath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
pathParts := strings.Split(dest, "/")
|
||||
numParts := len(pathParts)
|
||||
pciAddress := pathParts[numParts-3]
|
||||
card := &GraphicsCard{
|
||||
Address: pciAddress,
|
||||
Index: cardIdx,
|
||||
}
|
||||
cards = append(cards, card)
|
||||
}
|
||||
ctx.gpuFillNUMANodes(cards)
|
||||
ctx.gpuFillPCIDevice(cards)
|
||||
info.GraphicsCards = cards
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loops through each GraphicsCard struct and attempts to fill the DeviceInfo
|
||||
// attribute with PCI device information
|
||||
func (ctx *context) gpuFillPCIDevice(cards []*GraphicsCard) {
|
||||
pci, err := PCI()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, card := range cards {
|
||||
if card.DeviceInfo == nil {
|
||||
card.DeviceInfo = pci.GetDevice(card.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loops through each GraphicsCard struct and find which NUMA node the card is
|
||||
// affined to, setting the GraphicsCard.Node field accordingly. If the host
|
||||
// system is not a NUMA system, the Node field will be set to nil.
|
||||
func (ctx *context) gpuFillNUMANodes(cards []*GraphicsCard) {
|
||||
topo := &TopologyInfo{}
|
||||
if err := ctx.topologyFillInfo(topo); err != nil {
|
||||
for _, card := range cards {
|
||||
if topo.Architecture != ARCHITECTURE_NUMA {
|
||||
card.Node = nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, card := range cards {
|
||||
// Each graphics card on a NUMA system will have a pseudo-file
|
||||
// called /sys/class/drm/card$CARD_INDEX/device/numa_node which
|
||||
// contains the NUMA node that the card is affined to
|
||||
cardIndexStr := strconv.Itoa(card.Index)
|
||||
fpath := filepath.Join(
|
||||
ctx.pathSysClassDrm(),
|
||||
"card"+cardIndexStr,
|
||||
"device",
|
||||
"numa_node",
|
||||
)
|
||||
nodeIdx := safeIntFromFile(fpath)
|
||||
if nodeIdx == -1 {
|
||||
continue
|
||||
}
|
||||
for _, node := range topo.Nodes {
|
||||
if nodeIdx == int(node.Id) {
|
||||
card.Node = node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) gpuFillInfo(info *GPUInfo) error {
|
||||
return errors.New("gpuFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// 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"
|
||||
|
||||
"github.com/StackExchange/wmi"
|
||||
"github.com/jaypipes/pcidb"
|
||||
)
|
||||
|
||||
const wqlVideoController = "SELECT Caption, CreationClassName, Description, DeviceID, Name, PNPDeviceID, SystemCreationClassName, SystemName, VideoArchitecture, VideoMemoryType, VideoModeDescription, VideoProcessor FROM Win32_VideoController"
|
||||
|
||||
type win32VideoController struct {
|
||||
Caption string
|
||||
CreationClassName string
|
||||
Description string
|
||||
DeviceID string
|
||||
Name string
|
||||
PNPDeviceID string
|
||||
SystemCreationClassName string
|
||||
SystemName string
|
||||
VideoArchitecture uint16
|
||||
VideoMemoryType uint16
|
||||
VideoModeDescription string
|
||||
VideoProcessor string
|
||||
}
|
||||
|
||||
const wqlPnPEntity = "SELECT Caption, CreationClassName, Description, DeviceID, Manufacturer, Name, PNPClass, PNPDeviceID FROM Win32_PnPEntity"
|
||||
|
||||
type win32PnPEntity struct {
|
||||
Caption string
|
||||
CreationClassName string
|
||||
Description string
|
||||
DeviceID string
|
||||
Manufacturer string
|
||||
Name string
|
||||
PNPClass string
|
||||
PNPDeviceID string
|
||||
}
|
||||
|
||||
func (ctx *context) gpuFillInfo(info *GPUInfo) error {
|
||||
// Getting data from WMI
|
||||
var win32VideoControllerDescriptions []win32VideoController
|
||||
if err := wmi.Query(wqlVideoController, &win32VideoControllerDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Building dynamic WHERE clause with addresses to create a single query collecting all desired data
|
||||
queryAddresses := []string{}
|
||||
for _, description := range win32VideoControllerDescriptions {
|
||||
var queryAddres = strings.Replace(description.PNPDeviceID, "\\", `\\`, -1)
|
||||
queryAddresses = append(queryAddresses, "PNPDeviceID='"+queryAddres+"'")
|
||||
}
|
||||
whereClause := strings.Join(queryAddresses[:], " OR ")
|
||||
|
||||
// Getting data from WMI
|
||||
var win32PnPDescriptions []win32PnPEntity
|
||||
var wqlPnPDevice = wqlPnPEntity + " WHERE " + whereClause
|
||||
if err := wmi.Query(wqlPnPDevice, &win32PnPDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Converting into standard structures
|
||||
cards := make([]*GraphicsCard, 0)
|
||||
for _, description := range win32VideoControllerDescriptions {
|
||||
card := &GraphicsCard{
|
||||
Address: description.DeviceID, // https://stackoverflow.com/questions/32073667/how-do-i-discover-the-pcie-bus-topology-and-slot-numbers-on-the-board
|
||||
Index: 0,
|
||||
DeviceInfo: ctx.GetDevice(description.PNPDeviceID, win32PnPDescriptions),
|
||||
}
|
||||
cards = append(cards, card)
|
||||
}
|
||||
info.GraphicsCards = cards
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *context) GetDevice(id string, entities []win32PnPEntity) *PCIDevice {
|
||||
// Backslashing PnP address ID as requested by JSON and VMI query: https://docs.microsoft.com/en-us/windows/win32/wmisdk/where-clause
|
||||
var queryAddress = strings.Replace(id, "\\", `\\`, -1)
|
||||
// Preparing default structure
|
||||
var device = &PCIDevice{
|
||||
Address: queryAddress,
|
||||
Vendor: &pcidb.Vendor{
|
||||
ID: UNKNOWN,
|
||||
Name: UNKNOWN,
|
||||
Products: []*pcidb.Product{},
|
||||
},
|
||||
Subsystem: &pcidb.Product{
|
||||
ID: UNKNOWN,
|
||||
Name: UNKNOWN,
|
||||
Subsystems: []*pcidb.Product{},
|
||||
},
|
||||
Product: &pcidb.Product{
|
||||
ID: UNKNOWN,
|
||||
Name: UNKNOWN,
|
||||
Subsystems: []*pcidb.Product{},
|
||||
},
|
||||
Class: &pcidb.Class{
|
||||
ID: UNKNOWN,
|
||||
Name: UNKNOWN,
|
||||
Subclasses: []*pcidb.Subclass{},
|
||||
},
|
||||
Subclass: &pcidb.Subclass{
|
||||
ID: UNKNOWN,
|
||||
Name: UNKNOWN,
|
||||
ProgrammingInterfaces: []*pcidb.ProgrammingInterface{},
|
||||
},
|
||||
ProgrammingInterface: &pcidb.ProgrammingInterface{
|
||||
ID: UNKNOWN,
|
||||
Name: UNKNOWN,
|
||||
},
|
||||
}
|
||||
// If an entity is found we get its data inside the standard structure
|
||||
for _, description := range entities {
|
||||
if id == description.PNPDeviceID {
|
||||
device.Vendor.ID = description.Manufacturer
|
||||
device.Vendor.Name = description.Manufacturer
|
||||
device.Product.ID = description.Name
|
||||
device.Product.Name = description.Description
|
||||
break
|
||||
}
|
||||
}
|
||||
return device
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// 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 "fmt"
|
||||
|
||||
const (
|
||||
UNKNOWN = "unknown"
|
||||
)
|
||||
|
||||
// Concrete merged set of configuration switches that act as an execution
|
||||
// context when calling internal discovery methods
|
||||
type context struct {
|
||||
chroot string
|
||||
}
|
||||
|
||||
// HostInfo is a wrapper struct containing information about the host system's
|
||||
// memory, block storage, CPU, etc
|
||||
type HostInfo struct {
|
||||
Memory *MemoryInfo `json:"memory"`
|
||||
Block *BlockInfo `json:"block"`
|
||||
CPU *CPUInfo `json:"cpu"`
|
||||
Topology *TopologyInfo `json:"topology"`
|
||||
Network *NetworkInfo `json:"network"`
|
||||
GPU *GPUInfo `json:"gpu"`
|
||||
Chassis *ChassisInfo `json:"chassis"`
|
||||
BIOS *BIOSInfo `json:"bios"`
|
||||
Baseboard *BaseboardInfo `json:"baseboard"`
|
||||
Product *ProductInfo `json:"product"`
|
||||
}
|
||||
|
||||
// Host returns a pointer to a HostInfo struct that contains fields with
|
||||
// information about the host system's CPU, memory, network devices, etc
|
||||
func Host(opts ...*WithOption) (*HostInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
mem := &MemoryInfo{}
|
||||
if err := ctx.memFillInfo(mem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block := &BlockInfo{}
|
||||
if err := ctx.blockFillInfo(block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpu := &CPUInfo{}
|
||||
if err := ctx.cpuFillInfo(cpu); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
topology := &TopologyInfo{}
|
||||
if err := ctx.topologyFillInfo(topology); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
net := &NetworkInfo{}
|
||||
if err := ctx.netFillInfo(net); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gpu := &GPUInfo{}
|
||||
if err := ctx.gpuFillInfo(gpu); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chassis := &ChassisInfo{}
|
||||
if err := ctx.chassisFillInfo(chassis); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bios := &BIOSInfo{}
|
||||
if err := ctx.biosFillInfo(bios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseboard := &BaseboardInfo{}
|
||||
if err := ctx.baseboardFillInfo(baseboard); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
product := &ProductInfo{}
|
||||
if err := ctx.productFillInfo(product); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HostInfo{
|
||||
CPU: cpu,
|
||||
Memory: mem,
|
||||
Block: block,
|
||||
Topology: topology,
|
||||
Network: net,
|
||||
GPU: gpu,
|
||||
Chassis: chassis,
|
||||
BIOS: bios,
|
||||
Baseboard: baseboard,
|
||||
Product: product,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns a newline-separated output of the HostInfo's component
|
||||
// structs' String-ified output
|
||||
func (info *HostInfo) String() string {
|
||||
return fmt.Sprintf(
|
||||
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
|
||||
info.Block.String(),
|
||||
info.CPU.String(),
|
||||
info.GPU.String(),
|
||||
info.Memory.String(),
|
||||
info.Network.String(),
|
||||
info.Topology.String(),
|
||||
info.Chassis.String(),
|
||||
info.BIOS.String(),
|
||||
info.Baseboard.String(),
|
||||
info.Product.String(),
|
||||
)
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the host information formatted as YAML
|
||||
// under a top-level "host:" key
|
||||
func (i *HostInfo) YAMLString() string {
|
||||
return safeYAML(i)
|
||||
}
|
||||
|
||||
// JSONString returns a string with the host information formatted as JSON
|
||||
// under a top-level "host:" key
|
||||
func (i *HostInfo) JSONString(indent bool) string {
|
||||
return safeJSON(i, indent)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// safeYAML returns a string after marshalling the supplied parameter into YAML
|
||||
func safeYAML(p interface{}) string {
|
||||
b, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
warn("error marshalling JSON: %s", err)
|
||||
return ""
|
||||
}
|
||||
yb, err := yaml.JSONToYAML(b)
|
||||
if err != nil {
|
||||
warn("error converting JSON to YAML: %s", err)
|
||||
return ""
|
||||
}
|
||||
return string(yb)
|
||||
}
|
||||
|
||||
// safeJSON returns a string after marshalling the supplied parameter into
|
||||
// JSON. Accepts an optional argument to trigger pretty/indented formatting of
|
||||
// the JSON string
|
||||
func safeJSON(p interface{}, indent bool) string {
|
||||
var b []byte
|
||||
var err error
|
||||
if !indent {
|
||||
b, err = json.Marshal(p)
|
||||
} else {
|
||||
b, err = json.MarshalIndent(&p, "", " ")
|
||||
}
|
||||
if err != nil {
|
||||
warn("error marshalling JSON: %s", err)
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type MemoryModule struct {
|
||||
Label string `json:"label"`
|
||||
Location string `json:"location"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
SizeBytes int64 `json:"size_bytes"`
|
||||
Vendor string `json:"vendor"`
|
||||
}
|
||||
|
||||
type MemoryInfo struct {
|
||||
TotalPhysicalBytes int64 `json:"total_physical_bytes"`
|
||||
TotalUsableBytes int64 `json:"total_usable_bytes"`
|
||||
// An array of sizes, in bytes, of memory pages supported by the host
|
||||
SupportedPageSizes []uint64 `json:"supported_page_sizes"`
|
||||
Modules []*MemoryModule `json:"modules"`
|
||||
}
|
||||
|
||||
func Memory(opts ...*WithOption) (*MemoryInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &MemoryInfo{}
|
||||
if err := ctx.memFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (i *MemoryInfo) String() string {
|
||||
tpbs := UNKNOWN
|
||||
if i.TotalPhysicalBytes > 0 {
|
||||
tpb := i.TotalPhysicalBytes
|
||||
unit, unitStr := unitWithString(tpb)
|
||||
tpb = int64(math.Ceil(float64(i.TotalPhysicalBytes) / float64(unit)))
|
||||
tpbs = fmt.Sprintf("%d%s", tpb, unitStr)
|
||||
}
|
||||
tubs := UNKNOWN
|
||||
if i.TotalUsableBytes > 0 {
|
||||
tub := i.TotalUsableBytes
|
||||
unit, unitStr := unitWithString(tub)
|
||||
tub = int64(math.Ceil(float64(i.TotalUsableBytes) / float64(unit)))
|
||||
tubs = fmt.Sprintf("%d%s", tub, unitStr)
|
||||
}
|
||||
return fmt.Sprintf("memory (%s physical, %s usable)", tpbs, tubs)
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate memory information in a top-level
|
||||
// "memory" YAML/JSON map/object key
|
||||
type memoryPrinter struct {
|
||||
Info *MemoryInfo `json:"memory"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the memory information formatted as YAML
|
||||
// under a top-level "memory:" key
|
||||
func (i *MemoryInfo) YAMLString() string {
|
||||
return safeYAML(memoryPrinter{i})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the memory information formatted as JSON
|
||||
// under a top-level "memory:" key
|
||||
func (i *MemoryInfo) JSONString(indent bool) string {
|
||||
return safeJSON(memoryPrinter{i}, indent)
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MemoryCacheType int
|
||||
|
||||
const (
|
||||
MEMORY_CACHE_TYPE_UNIFIED MemoryCacheType = iota
|
||||
MEMORY_CACHE_TYPE_INSTRUCTION
|
||||
MEMORY_CACHE_TYPE_DATA
|
||||
)
|
||||
|
||||
var (
|
||||
memoryCacheTypeString = map[MemoryCacheType]string{
|
||||
MEMORY_CACHE_TYPE_UNIFIED: "Unified",
|
||||
MEMORY_CACHE_TYPE_INSTRUCTION: "Instruction",
|
||||
MEMORY_CACHE_TYPE_DATA: "Data",
|
||||
}
|
||||
)
|
||||
|
||||
func (a MemoryCacheType) String() string {
|
||||
return memoryCacheTypeString[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 MemoryCacheType) MarshalJSON() ([]byte, error) {
|
||||
return []byte("\"" + strings.ToLower(a.String()) + "\""), nil
|
||||
}
|
||||
|
||||
type SortByMemoryCacheLevelTypeFirstProcessor []*MemoryCache
|
||||
|
||||
func (a SortByMemoryCacheLevelTypeFirstProcessor) Len() int { return len(a) }
|
||||
func (a SortByMemoryCacheLevelTypeFirstProcessor) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a SortByMemoryCacheLevelTypeFirstProcessor) Less(i, j int) bool {
|
||||
if a[i].Level < a[j].Level {
|
||||
return true
|
||||
} else if a[i].Level == a[j].Level {
|
||||
if a[i].Type < a[j].Type {
|
||||
return true
|
||||
} else if a[i].Type == a[j].Type {
|
||||
// NOTE(jaypipes): len(LogicalProcessors) is always >0 and is always
|
||||
// sorted lowest LP ID to highest LP ID
|
||||
return a[i].LogicalProcessors[0] < a[j].LogicalProcessors[0]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SortByLogicalProcessorId []uint32
|
||||
|
||||
func (a SortByLogicalProcessorId) Len() int { return len(a) }
|
||||
func (a SortByLogicalProcessorId) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a SortByLogicalProcessorId) Less(i, j int) bool { return a[i] < a[j] }
|
||||
|
||||
type MemoryCache struct {
|
||||
Level uint8 `json:"level"`
|
||||
Type MemoryCacheType `json:"type"`
|
||||
SizeBytes uint64 `json:"size_bytes"`
|
||||
// The set of logical processors (hardware threads) that have access to the
|
||||
// cache
|
||||
LogicalProcessors []uint32 `json:"logical_processors"`
|
||||
}
|
||||
|
||||
func (c *MemoryCache) String() string {
|
||||
sizeKb := c.SizeBytes / uint64(KB)
|
||||
typeStr := ""
|
||||
if c.Type == MEMORY_CACHE_TYPE_INSTRUCTION {
|
||||
typeStr = "i"
|
||||
} else if c.Type == MEMORY_CACHE_TYPE_DATA {
|
||||
typeStr = "d"
|
||||
}
|
||||
cacheIdStr := fmt.Sprintf("L%d%s", c.Level, typeStr)
|
||||
processorMapStr := ""
|
||||
if c.LogicalProcessors != nil {
|
||||
lpStrings := make([]string, len(c.LogicalProcessors))
|
||||
for x, lpid := range c.LogicalProcessors {
|
||||
lpStrings[x] = strconv.Itoa(int(lpid))
|
||||
}
|
||||
processorMapStr = " shared with logical processors: " + strings.Join(lpStrings, ",")
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s cache (%d KB)%s",
|
||||
cacheIdStr,
|
||||
sizeKb,
|
||||
processorMapStr,
|
||||
)
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (ctx *context) cachesForNode(nodeID int) ([]*MemoryCache, error) {
|
||||
// The /sys/devices/node/nodeX directory contains a subdirectory called
|
||||
// 'cpuX' for each logical processor assigned to the node. Each of those
|
||||
// subdirectories containers a 'cache' subdirectory which contains a number
|
||||
// of subdirectories beginning with 'index' and ending in the cache's
|
||||
// internal 0-based identifier. Those subdirectories contain a number of
|
||||
// files, including 'shared_cpu_list', 'size', and 'type' which we use to
|
||||
// determine cache characteristics.
|
||||
path := filepath.Join(
|
||||
ctx.pathSysDevicesSystemNode(),
|
||||
fmt.Sprintf("node%d", nodeID),
|
||||
)
|
||||
caches := make(map[string]*MemoryCache)
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !strings.HasPrefix(filename, "cpu") {
|
||||
continue
|
||||
}
|
||||
if filename == "cpumap" || filename == "cpulist" {
|
||||
// There are two files in the node directory that start with 'cpu'
|
||||
// but are not subdirectories ('cpulist' and 'cpumap'). Ignore
|
||||
// these files.
|
||||
continue
|
||||
}
|
||||
// Grab the logical processor ID by cutting the integer from the
|
||||
// /sys/devices/system/node/nodeX/cpuX filename
|
||||
cpuPath := filepath.Join(path, filename)
|
||||
lpID, _ := strconv.Atoi(filename[3:])
|
||||
|
||||
// Inspect the caches for each logical processor. There will be a
|
||||
// /sys/devices/system/node/nodeX/cpuX/cache directory containing a
|
||||
// number of directories beginning with the prefix "index" followed by
|
||||
// a number. The number indicates the level of the cache, which
|
||||
// indicates the "distance" from the processor. Each of these
|
||||
// directories contains information about the size of that level of
|
||||
// cache and the processors mapped to it.
|
||||
cachePath := filepath.Join(cpuPath, "cache")
|
||||
if _, err = os.Stat(cachePath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
cacheDirFiles, err := ioutil.ReadDir(cachePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, cacheDirFile := range cacheDirFiles {
|
||||
cacheDirFileName := cacheDirFile.Name()
|
||||
if !strings.HasPrefix(cacheDirFileName, "index") {
|
||||
continue
|
||||
}
|
||||
cacheIndex, _ := strconv.Atoi(cacheDirFileName[5:])
|
||||
|
||||
// The cache information is repeated for each node, so here, we
|
||||
// just ensure that we only have a one MemoryCache object for each
|
||||
// unique combination of level, type and processor map
|
||||
level := ctx.memoryCacheLevel(nodeID, lpID, cacheIndex)
|
||||
cacheType := ctx.memoryCacheType(nodeID, lpID, cacheIndex)
|
||||
sharedCpuMap := ctx.memoryCacheSharedCPUMap(nodeID, lpID, cacheIndex)
|
||||
cacheKey := fmt.Sprintf("%d-%d-%s", level, cacheType, sharedCpuMap)
|
||||
|
||||
cache, exists := caches[cacheKey]
|
||||
if !exists {
|
||||
size := ctx.memoryCacheSize(nodeID, lpID, level)
|
||||
cache = &MemoryCache{
|
||||
Level: uint8(level),
|
||||
Type: cacheType,
|
||||
SizeBytes: uint64(size) * uint64(KB),
|
||||
LogicalProcessors: make([]uint32, 0),
|
||||
}
|
||||
caches[cacheKey] = cache
|
||||
}
|
||||
cache.LogicalProcessors = append(
|
||||
cache.LogicalProcessors,
|
||||
uint32(lpID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
cacheVals := make([]*MemoryCache, len(caches))
|
||||
x := 0
|
||||
for _, c := range caches {
|
||||
// ensure the cache's processor set is sorted by logical process ID
|
||||
sort.Sort(SortByLogicalProcessorId(c.LogicalProcessors))
|
||||
cacheVals[x] = c
|
||||
x++
|
||||
}
|
||||
|
||||
return cacheVals, nil
|
||||
}
|
||||
|
||||
func (ctx *context) pathNodeCPU(nodeID int, lpID int) string {
|
||||
return filepath.Join(
|
||||
ctx.pathSysDevicesSystemNode(),
|
||||
fmt.Sprintf("node%d", nodeID),
|
||||
fmt.Sprintf("cpu%d", lpID),
|
||||
)
|
||||
}
|
||||
|
||||
func (ctx *context) pathNodeCPUCache(nodeID int, lpID int) string {
|
||||
return filepath.Join(
|
||||
ctx.pathNodeCPU(nodeID, lpID),
|
||||
"cache",
|
||||
)
|
||||
}
|
||||
|
||||
func (ctx *context) pathNodeCPUCacheIndex(nodeID int, lpID int, cacheIndex int) string {
|
||||
return filepath.Join(
|
||||
ctx.pathNodeCPUCache(nodeID, lpID),
|
||||
fmt.Sprintf("index%d", cacheIndex),
|
||||
)
|
||||
}
|
||||
|
||||
func (ctx *context) memoryCacheLevel(nodeID int, lpID int, cacheIndex int) int {
|
||||
levelPath := filepath.Join(
|
||||
ctx.pathNodeCPUCacheIndex(nodeID, lpID, cacheIndex),
|
||||
"level",
|
||||
)
|
||||
levelContents, err := ioutil.ReadFile(levelPath)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
return -1
|
||||
}
|
||||
// levelContents is now a []byte with the last byte being a newline
|
||||
// character. Trim that off and convert the contents to an integer.
|
||||
level, err := strconv.Atoi(string(levelContents[:len(levelContents)-1]))
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to parse int from %s\n", levelContents)
|
||||
return -1
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
func (ctx *context) memoryCacheSize(nodeID int, lpID int, cacheIndex int) int {
|
||||
sizePath := filepath.Join(
|
||||
ctx.pathNodeCPUCacheIndex(nodeID, lpID, cacheIndex),
|
||||
"size",
|
||||
)
|
||||
sizeContents, err := ioutil.ReadFile(sizePath)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
return -1
|
||||
}
|
||||
// size comes as XK\n, so we trim off the K and the newline.
|
||||
size, err := strconv.Atoi(string(sizeContents[:len(sizeContents)-2]))
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to parse int from %s\n", sizeContents)
|
||||
return -1
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (ctx *context) memoryCacheType(nodeID int, lpID int, cacheIndex int) MemoryCacheType {
|
||||
typePath := filepath.Join(
|
||||
ctx.pathNodeCPUCacheIndex(nodeID, lpID, cacheIndex),
|
||||
"type",
|
||||
)
|
||||
cacheTypeContents, err := ioutil.ReadFile(typePath)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
return MEMORY_CACHE_TYPE_UNIFIED
|
||||
}
|
||||
switch string(cacheTypeContents[:len(cacheTypeContents)-1]) {
|
||||
case "Data":
|
||||
return MEMORY_CACHE_TYPE_DATA
|
||||
case "Instruction":
|
||||
return MEMORY_CACHE_TYPE_INSTRUCTION
|
||||
default:
|
||||
return MEMORY_CACHE_TYPE_UNIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *context) memoryCacheSharedCPUMap(nodeID int, lpID int, cacheIndex int) string {
|
||||
scpuPath := filepath.Join(
|
||||
ctx.pathNodeCPUCacheIndex(nodeID, lpID, cacheIndex),
|
||||
"shared_cpu_map",
|
||||
)
|
||||
sharedCpuMap, err := ioutil.ReadFile(scpuPath)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
return ""
|
||||
}
|
||||
return string(sharedCpuMap[:len(sharedCpuMap)-1])
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
_WARN_CANNOT_DETERMINE_PHYSICAL_MEMORY = `
|
||||
Could not determine total physical bytes of memory. This may
|
||||
be due to the host being a virtual machine or container with no
|
||||
/var/log/syslog file, or the current user may not have necessary
|
||||
privileges to read the syslog. We are falling back to setting the
|
||||
total physical amount of memory to the total usable amount of memory
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
// System log lines will look similar to the following:
|
||||
// ... kernel: [0.000000] Memory: 24633272K/25155024K ...
|
||||
_REGEX_SYSLOG_MEMLINE = regexp.MustCompile(`Memory:\s+\d+K\/(\d+)K`)
|
||||
)
|
||||
|
||||
func (ctx *context) memFillInfo(info *MemoryInfo) error {
|
||||
tub := ctx.memTotalUsableBytes()
|
||||
if tub < 1 {
|
||||
return fmt.Errorf("Could not determine total usable bytes of memory")
|
||||
}
|
||||
info.TotalUsableBytes = tub
|
||||
tpb := ctx.memTotalPhysicalBytes()
|
||||
info.TotalPhysicalBytes = tpb
|
||||
if tpb < 1 {
|
||||
warn(_WARN_CANNOT_DETERMINE_PHYSICAL_MEMORY)
|
||||
info.TotalPhysicalBytes = tub
|
||||
}
|
||||
info.SupportedPageSizes = ctx.memSupportedPageSizes()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *context) memTotalPhysicalBytes() int64 {
|
||||
// In Linux, the total physical memory can be determined by looking at the
|
||||
// output of dmidecode, however dmidecode requires root privileges to run,
|
||||
// so instead we examine the system logs for startup information containing
|
||||
// total physical memory and cache the results of this.
|
||||
findPhysicalKb := func(line string) int64 {
|
||||
matches := _REGEX_SYSLOG_MEMLINE.FindStringSubmatch(line)
|
||||
if len(matches) == 2 {
|
||||
i, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int64(i * 1024)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// /var/log will contain a file called syslog and 0 or more files called
|
||||
// syslog.$NUMBER or syslog.$NUMBER.gz containing system log records. We
|
||||
// search each, stopping when we match a system log record line that
|
||||
// contains physical memory information.
|
||||
logDir := ctx.pathVarLog()
|
||||
logFiles, err := ioutil.ReadDir(logDir)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
for _, file := range logFiles {
|
||||
if strings.HasPrefix(file.Name(), "syslog") {
|
||||
fullPath := filepath.Join(logDir, file.Name())
|
||||
unzip := strings.HasSuffix(file.Name(), ".gz")
|
||||
var r io.ReadCloser
|
||||
r, err = os.Open(fullPath)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
defer safeClose(r)
|
||||
if unzip {
|
||||
r, err = gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
size := findPhysicalKb(line)
|
||||
if size > 0 {
|
||||
return size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (ctx *context) memTotalUsableBytes() int64 {
|
||||
// In Linux, /proc/meminfo contains a set of memory-related amounts, with
|
||||
// lines looking like the following:
|
||||
//
|
||||
// $ cat /proc/meminfo
|
||||
// MemTotal: 24677596 kB
|
||||
// MemFree: 21244356 kB
|
||||
// MemAvailable: 22085432 kB
|
||||
// ...
|
||||
// HugePages_Total: 0
|
||||
// HugePages_Free: 0
|
||||
// HugePages_Rsvd: 0
|
||||
// HugePages_Surp: 0
|
||||
// ...
|
||||
//
|
||||
// It's worth noting that /proc/meminfo returns exact information, not
|
||||
// "theoretical" information. For instance, on the above system, I have
|
||||
// 24GB of RAM but MemTotal is indicating only around 23GB. This is because
|
||||
// MemTotal contains the exact amount of *usable* memory after accounting
|
||||
// for the kernel's resident memory size and a few reserved bits. For more
|
||||
// information, see:
|
||||
//
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
filePath := ctx.pathProcMeminfo()
|
||||
r, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
defer safeClose(r)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Fields(line)
|
||||
key := strings.Trim(parts[0], ": \t")
|
||||
if key != "MemTotal" {
|
||||
continue
|
||||
}
|
||||
value, err := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
inKb := (len(parts) == 3 && strings.TrimSpace(parts[2]) == "kB")
|
||||
if inKb {
|
||||
value = value * int(KB)
|
||||
}
|
||||
return int64(value)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (ctx *context) memSupportedPageSizes() []uint64 {
|
||||
// In Linux, /sys/kernel/mm/hugepages contains a directory per page size
|
||||
// supported by the kernel. The directory name corresponds to the pattern
|
||||
// 'hugepages-{pagesize}kb'
|
||||
dir := ctx.pathSysKernelMMHugepages()
|
||||
out := make([]uint64, 0)
|
||||
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
for _, file := range files {
|
||||
parts := strings.Split(file.Name(), "-")
|
||||
sizeStr := parts[1]
|
||||
// Cut off the 'kb'
|
||||
sizeStr = sizeStr[0 : len(sizeStr)-2]
|
||||
size, err := strconv.Atoi(sizeStr)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
out = append(out, uint64(size*int(KB)))
|
||||
}
|
||||
return out
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) memFillInfo(info *MemoryInfo) error {
|
||||
return errors.New("memFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// 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 (
|
||||
"github.com/StackExchange/wmi"
|
||||
)
|
||||
|
||||
const wqlOperatingSystem = "SELECT TotalVisibleMemorySize FROM Win32_OperatingSystem"
|
||||
|
||||
type win32OperatingSystem struct {
|
||||
TotalVisibleMemorySize *uint64
|
||||
}
|
||||
|
||||
const wqlPhysicalMemory = "SELECT BankLabel, Capacity, DataWidth, Description, DeviceLocator, Manufacturer, Model, Name, PartNumber, PositionInRow, SerialNumber, Speed, Tag, TotalWidth FROM Win32_PhysicalMemory"
|
||||
|
||||
type win32PhysicalMemory struct {
|
||||
BankLabel *string
|
||||
Capacity *uint64
|
||||
DataWidth *uint16
|
||||
Description *string
|
||||
DeviceLocator *string
|
||||
Manufacturer *string
|
||||
Model *string
|
||||
Name *string
|
||||
PartNumber *string
|
||||
PositionInRow *uint32
|
||||
SerialNumber *string
|
||||
Speed *uint32
|
||||
Tag *string
|
||||
TotalWidth *uint16
|
||||
}
|
||||
|
||||
func (ctx *context) memFillInfo(info *MemoryInfo) error {
|
||||
// Getting info from WMI
|
||||
var win32OSDescriptions []win32OperatingSystem
|
||||
if err := wmi.Query(wqlOperatingSystem, &win32OSDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
var win32MemDescriptions []win32PhysicalMemory
|
||||
if err := wmi.Query(wqlPhysicalMemory, &win32MemDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
// We calculate total physical memory size by summing the DIMM sizes
|
||||
var totalPhysicalBytes uint64
|
||||
info.Modules = make([]*MemoryModule, 0, len(win32MemDescriptions))
|
||||
for _, description := range win32MemDescriptions {
|
||||
totalPhysicalBytes += *description.Capacity
|
||||
info.Modules = append(info.Modules, &MemoryModule{
|
||||
Label: *description.BankLabel,
|
||||
Location: *description.DeviceLocator,
|
||||
SerialNumber: *description.SerialNumber,
|
||||
SizeBytes: int64(*description.Capacity),
|
||||
Vendor: *description.Manufacturer,
|
||||
})
|
||||
}
|
||||
var totalUsableBytes uint64
|
||||
for _, description := range win32OSDescriptions {
|
||||
// TotalVisibleMemorySize is the amount of memory available for us by
|
||||
// the operating system **in Kilobytes**
|
||||
totalUsableBytes += *description.TotalVisibleMemorySize * uint64(KB)
|
||||
}
|
||||
info.TotalUsableBytes = int64(totalUsableBytes)
|
||||
info.TotalPhysicalBytes = int64(totalPhysicalBytes)
|
||||
return nil
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type NICCapability struct {
|
||||
Name string `json:"name"`
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
CanEnable bool `json:"can_enable"`
|
||||
}
|
||||
|
||||
type NIC struct {
|
||||
Name string `json:"name"`
|
||||
MacAddress string `json:"mac_address"`
|
||||
IsVirtual bool `json:"is_virtual"`
|
||||
Capabilities []*NICCapability `json:"capabilities"`
|
||||
// TODO(jaypipes): Add PCI field for accessing PCI device information
|
||||
// PCI *PCIDevice `json:"pci"`
|
||||
}
|
||||
|
||||
func (n *NIC) String() string {
|
||||
isVirtualStr := ""
|
||||
if n.IsVirtual {
|
||||
isVirtualStr = " (virtual)"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s%s",
|
||||
n.Name,
|
||||
isVirtualStr,
|
||||
)
|
||||
}
|
||||
|
||||
type NetworkInfo struct {
|
||||
NICs []*NIC `json:"nics"`
|
||||
}
|
||||
|
||||
func Network(opts ...*WithOption) (*NetworkInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &NetworkInfo{}
|
||||
if err := ctx.netFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (i *NetworkInfo) String() string {
|
||||
return fmt.Sprintf(
|
||||
"net (%d NICs)",
|
||||
len(i.NICs),
|
||||
)
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate net information in a
|
||||
// top-level "net" YAML/JSON map/object key
|
||||
type netPrinter struct {
|
||||
Info *NetworkInfo `json:"network"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the net information formatted as YAML
|
||||
// under a top-level "net:" key
|
||||
func (i *NetworkInfo) YAMLString() string {
|
||||
return safeYAML(netPrinter{i})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the net information formatted as JSON
|
||||
// under a top-level "net:" key
|
||||
func (i *NetworkInfo) JSONString(indent bool) string {
|
||||
return safeJSON(netPrinter{i}, indent)
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
_WARN_ETHTOOL_NOT_INSTALLED = `ethtool not installed. Cannot grab NIC capabilities`
|
||||
)
|
||||
|
||||
func (ctx *context) netFillInfo(info *NetworkInfo) error {
|
||||
info.NICs = ctx.nics()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NICS has been deprecated in 0.2. Please use the NetworkInfo.NICs attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func NICs() []*NIC {
|
||||
msg := `
|
||||
The NICs() function has been DEPRECATED and will be removed in the 1.0 release
|
||||
of ghw. Please use the NetworkInfo.NICs attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.nics()
|
||||
}
|
||||
|
||||
func (ctx *context) nics() []*NIC {
|
||||
nics := make([]*NIC, 0)
|
||||
|
||||
files, err := ioutil.ReadDir(ctx.pathSysClassNet())
|
||||
if err != nil {
|
||||
return nics
|
||||
}
|
||||
|
||||
etInstalled := ethtoolInstalled()
|
||||
if !etInstalled {
|
||||
warn(_WARN_ETHTOOL_NOT_INSTALLED)
|
||||
}
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
// Ignore loopback...
|
||||
if filename == "lo" {
|
||||
continue
|
||||
}
|
||||
|
||||
netPath := filepath.Join(ctx.pathSysClassNet(), filename)
|
||||
dest, _ := os.Readlink(netPath)
|
||||
isVirtual := false
|
||||
if strings.Contains(dest, "devices/virtual/net") {
|
||||
isVirtual = true
|
||||
}
|
||||
|
||||
nic := &NIC{
|
||||
Name: filename,
|
||||
IsVirtual: isVirtual,
|
||||
}
|
||||
|
||||
mac := ctx.netDeviceMacAddress(filename)
|
||||
nic.MacAddress = mac
|
||||
if etInstalled {
|
||||
nic.Capabilities = ctx.netDeviceCapabilities(filename)
|
||||
} else {
|
||||
nic.Capabilities = []*NICCapability{}
|
||||
}
|
||||
nics = append(nics, nic)
|
||||
}
|
||||
return nics
|
||||
}
|
||||
|
||||
func (ctx *context) netDeviceMacAddress(dev string) string {
|
||||
// Instead of use udevadm, we can get the device's MAC address by examing
|
||||
// the /sys/class/net/$DEVICE/address file in sysfs. However, for devices
|
||||
// that have addr_assign_type != 0, return None since the MAC address is
|
||||
// random.
|
||||
aatPath := filepath.Join(ctx.pathSysClassNet(), dev, "addr_assign_type")
|
||||
contents, err := ioutil.ReadFile(aatPath)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if strings.TrimSpace(string(contents)) != "0" {
|
||||
return ""
|
||||
}
|
||||
addrPath := filepath.Join(ctx.pathSysClassNet(), dev, "address")
|
||||
contents, err = ioutil.ReadFile(addrPath)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(contents))
|
||||
}
|
||||
|
||||
func ethtoolInstalled() bool {
|
||||
_, err := exec.LookPath("ethtool")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (ctx *context) netDeviceCapabilities(dev string) []*NICCapability {
|
||||
caps := make([]*NICCapability, 0)
|
||||
path, _ := exec.LookPath("ethtool")
|
||||
cmd := exec.Command(path, "-k", dev)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("could not grab NIC capabilities for %s: %s", dev, err)
|
||||
warn(msg)
|
||||
return caps
|
||||
}
|
||||
|
||||
// The out variable will now contain something that looks like the
|
||||
// following.
|
||||
//
|
||||
// Features for enp58s0f1:
|
||||
// rx-checksumming: on
|
||||
// tx-checksumming: off
|
||||
// tx-checksum-ipv4: off
|
||||
// tx-checksum-ip-generic: off [fixed]
|
||||
// tx-checksum-ipv6: off
|
||||
// tx-checksum-fcoe-crc: off [fixed]
|
||||
// tx-checksum-sctp: off [fixed]
|
||||
// scatter-gather: off
|
||||
// tx-scatter-gather: off
|
||||
// tx-scatter-gather-fraglist: off [fixed]
|
||||
// tcp-segmentation-offload: off
|
||||
// tx-tcp-segmentation: off
|
||||
// tx-tcp-ecn-segmentation: off [fixed]
|
||||
// tx-tcp-mangleid-segmentation: off
|
||||
// tx-tcp6-segmentation: off
|
||||
// < snipped >
|
||||
scanner := bufio.NewScanner(&out)
|
||||
// Skip the first line...
|
||||
scanner.Scan()
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimPrefix(scanner.Text(), "\t")
|
||||
caps = append(caps, netParseEthtoolFeature(line))
|
||||
}
|
||||
return caps
|
||||
}
|
||||
|
||||
// netParseEthtoolFeature parses a line from the ethtool -k output and returns
|
||||
// a NICCapability.
|
||||
//
|
||||
// The supplied line will look like the following:
|
||||
//
|
||||
// tx-checksum-ip-generic: off [fixed]
|
||||
//
|
||||
// [fixed] indicates that the feature may not be turned on/off. Note: it makes
|
||||
// no difference whether a privileged user runs `ethtool -k` when determining
|
||||
// whether [fixed] appears for a feature.
|
||||
func netParseEthtoolFeature(line string) *NICCapability {
|
||||
parts := strings.Fields(line)
|
||||
cap := strings.TrimSuffix(parts[0], ":")
|
||||
enabled := parts[1] == "on"
|
||||
fixed := len(parts) == 3 && parts[2] == "[fixed]"
|
||||
return &NICCapability{
|
||||
Name: cap,
|
||||
IsEnabled: enabled,
|
||||
CanEnable: !fixed,
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) netFillInfo(info *NetworkInfo) error {
|
||||
return errors.New("netFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
|
||||
// NICS has been deprecated in 0.2. Please use the NetworkInfo.NICs attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func NICs() []*NIC {
|
||||
msg := `
|
||||
The NICs() function has been DEPRECATED and will be removed in the 1.0 release
|
||||
of ghw. Please use the NetworkInfo.NICs attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.nics()
|
||||
}
|
||||
|
||||
func (ctx *context) nics() []*NIC {
|
||||
return nil
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// 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"
|
||||
|
||||
"github.com/StackExchange/wmi"
|
||||
)
|
||||
|
||||
const wqlNetworkAdapter = "SELECT Description, DeviceID, Index, InterfaceIndex, MACAddress, Manufacturer, Name, NetConnectionID, ProductName, ServiceName FROM Win32_NetworkAdapter"
|
||||
|
||||
type win32NetworkAdapter struct {
|
||||
Description *string
|
||||
DeviceID *string
|
||||
Index *uint32
|
||||
InterfaceIndex *uint32
|
||||
MACAddress *string
|
||||
Manufacturer *string
|
||||
Name *string
|
||||
NetConnectionID *string
|
||||
ProductName *string
|
||||
ServiceName *string
|
||||
}
|
||||
|
||||
func (ctx *context) netFillInfo(info *NetworkInfo) error {
|
||||
// Getting info from WMI
|
||||
var win32NetDescriptions []win32NetworkAdapter
|
||||
if err := wmi.Query(wqlNetworkAdapter, &win32NetDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info.NICs = ctx.nics(win32NetDescriptions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *context) nics(win32NetDescriptions []win32NetworkAdapter) []*NIC {
|
||||
// Converting into standard structures
|
||||
nics := make([]*NIC, 0)
|
||||
for _, nicDescription := range win32NetDescriptions {
|
||||
nic := &NIC{
|
||||
Name: ctx.netDeviceName(nicDescription),
|
||||
MacAddress: *nicDescription.MACAddress,
|
||||
IsVirtual: false,
|
||||
Capabilities: []*NICCapability{},
|
||||
}
|
||||
// Appenging NIC to NICs
|
||||
nics = append(nics, nic)
|
||||
}
|
||||
|
||||
return nics
|
||||
}
|
||||
|
||||
func (ctx *context) netDeviceName(description win32NetworkAdapter) string {
|
||||
var name string
|
||||
if strings.TrimSpace(*description.NetConnectionID) != "" {
|
||||
name = *description.NetConnectionID + " - " + *description.Description
|
||||
} else {
|
||||
name = *description.Description
|
||||
}
|
||||
return name
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package ghw
|
||||
|
||||
import "os"
|
||||
|
||||
const (
|
||||
defaultChroot = "/"
|
||||
envKeyChroot = "GHW_CHROOT"
|
||||
)
|
||||
|
||||
// optDefaultChroot returns the value of the GHW_CHROOT environs variable or
|
||||
// the default value of "/" if not set
|
||||
func envOrDefaultChroot() string {
|
||||
// Grab options from the environs by default
|
||||
if val, exists := os.LookupEnv(envKeyChroot); exists {
|
||||
return val
|
||||
}
|
||||
return defaultChroot
|
||||
}
|
||||
|
||||
// WithOption is used to represent optionally-configured settings. Each field
|
||||
// is a pointer to some concrete value so that we can tell when something has
|
||||
// been set or left unset.
|
||||
type WithOption struct {
|
||||
// To facilitate querying of sysfs filesystems that are bind-mounted to a
|
||||
// non-default root mountpoint, we allow users to set the GHW_CHROOT environ
|
||||
// vairable to an alternate mountpoint. For instance, assume that the user of
|
||||
// ghw is a Golang binary being executed from an application container that has
|
||||
// certain host filesystems bind-mounted into the container at /host. The user
|
||||
// would ensure the GHW_CHROOT environ variable is set to "/host" and ghw will
|
||||
// build its paths from that location instead of /
|
||||
Chroot *string
|
||||
}
|
||||
|
||||
func WithChroot(dir string) *WithOption {
|
||||
return &WithOption{Chroot: &dir}
|
||||
}
|
||||
|
||||
func mergeOptions(opts ...*WithOption) *WithOption {
|
||||
merged := &WithOption{}
|
||||
for _, opt := range opts {
|
||||
if opt.Chroot != nil {
|
||||
merged.Chroot = opt.Chroot
|
||||
}
|
||||
}
|
||||
// Set the default value if missing from mergeOpts
|
||||
if merged.Chroot == nil {
|
||||
chroot := envOrDefaultChroot()
|
||||
merged.Chroot = &chroot
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// contextFromEnv returns a pointer to a context struct that has been populated
|
||||
// from the environs or default options values
|
||||
func contextFromEnv() *context {
|
||||
return &context{
|
||||
chroot: envOrDefaultChroot(),
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// 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 (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (ctx *context) pathVarLog() string {
|
||||
return filepath.Join(ctx.chroot, "var", "log")
|
||||
}
|
||||
|
||||
func (ctx *context) pathProcMeminfo() string {
|
||||
return filepath.Join(ctx.chroot, "proc", "meminfo")
|
||||
}
|
||||
|
||||
func (ctx *context) pathSysKernelMMHugepages() string {
|
||||
return filepath.Join(ctx.chroot, "sys", "kernel", "mm", "hugepages")
|
||||
}
|
||||
|
||||
func (ctx *context) pathProcCpuinfo() string {
|
||||
return filepath.Join(ctx.chroot, "proc", "cpuinfo")
|
||||
}
|
||||
|
||||
func (ctx *context) pathEtcMtab() string {
|
||||
return filepath.Join(ctx.chroot, "etc", "mtab")
|
||||
}
|
||||
|
||||
func (ctx *context) pathSysBlock() string {
|
||||
return filepath.Join(ctx.chroot, "sys", "block")
|
||||
}
|
||||
|
||||
func (ctx *context) pathSysDevicesSystemNode() string {
|
||||
return filepath.Join(ctx.chroot, "sys", "devices", "system", "node")
|
||||
}
|
||||
|
||||
func (ctx *context) pathSysBusPciDevices() string {
|
||||
return filepath.Join(ctx.chroot, "sys", "bus", "pci", "devices")
|
||||
}
|
||||
|
||||
func (ctx *context) pathSysClassDrm() string {
|
||||
return filepath.Join(ctx.chroot, "sys", "class", "drm")
|
||||
}
|
||||
|
||||
func (ctx *context) pathSysClassDMI() string {
|
||||
return filepath.Join(ctx.chroot, "sys", "class", "dmi")
|
||||
}
|
||||
|
||||
func (ctx *context) pathSysClassNet() string {
|
||||
return filepath.Join(ctx.chroot, "sys", "class", "net")
|
||||
}
|
||||
|
||||
func (ctx *context) pathRunUdevData() string {
|
||||
return filepath.Join(ctx.chroot, "run", "udev", "data")
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jaypipes/pcidb"
|
||||
)
|
||||
|
||||
var (
|
||||
regexPCIAddress *regexp.Regexp = regexp.MustCompile(
|
||||
`^(([0-9a-f]{0,4}):)?([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`,
|
||||
)
|
||||
)
|
||||
|
||||
type PCIDevice struct {
|
||||
// The PCI address of the device
|
||||
Address string `json:"address"`
|
||||
Vendor *pcidb.Vendor `json:"vendor"`
|
||||
Product *pcidb.Product `json:"product"`
|
||||
Subsystem *pcidb.Product `json:"subsystem"`
|
||||
// optional subvendor/sub-device information
|
||||
Class *pcidb.Class `json:"class"`
|
||||
// optional sub-class for the device
|
||||
Subclass *pcidb.Subclass `json:"subclass"`
|
||||
// optional programming interface
|
||||
ProgrammingInterface *pcidb.ProgrammingInterface `json:"programming_interface"`
|
||||
}
|
||||
|
||||
// NOTE(jaypipes) PCIDevice has a custom JSON marshaller because we don't want
|
||||
// to serialize the entire PCIDB information for the Vendor (which includes all
|
||||
// of the vendor's products, etc). Instead, we simply serialize the ID and
|
||||
// human-readable name of the vendor, product, class, etc.
|
||||
func (pd *PCIDevice) MarshalJSON() ([]byte, error) {
|
||||
b := bytes.NewBufferString("{")
|
||||
b.WriteString(fmt.Sprintf("\"address\":\"%s\"", pd.Address))
|
||||
b.WriteString(",\"vendor\": {")
|
||||
b.WriteString(
|
||||
fmt.Sprintf(
|
||||
"\"id\":\"%s\",\"name\":\"%s\"",
|
||||
pd.Vendor.ID,
|
||||
pd.Vendor.Name,
|
||||
),
|
||||
)
|
||||
b.WriteString("},")
|
||||
b.WriteString("\"product\": {")
|
||||
b.WriteString(
|
||||
fmt.Sprintf(
|
||||
"\"id\":\"%s\",\"name\":\"%s\"",
|
||||
pd.Product.ID,
|
||||
pd.Product.Name,
|
||||
),
|
||||
)
|
||||
b.WriteString("},")
|
||||
b.WriteString("\"subsystem\": {")
|
||||
b.WriteString(
|
||||
fmt.Sprintf(
|
||||
"\"id\":\"%s\",\"name\":\"%s\"",
|
||||
pd.Subsystem.ID,
|
||||
pd.Subsystem.Name,
|
||||
),
|
||||
)
|
||||
b.WriteString("},")
|
||||
b.WriteString("\"class\": {")
|
||||
b.WriteString(
|
||||
fmt.Sprintf(
|
||||
"\"id\":\"%s\",\"name\":\"%s\"",
|
||||
pd.Class.ID,
|
||||
pd.Class.Name,
|
||||
),
|
||||
)
|
||||
b.WriteString("},")
|
||||
b.WriteString("\"subclass\": {")
|
||||
b.WriteString(
|
||||
fmt.Sprintf(
|
||||
"\"id\":\"%s\",\"name\":\"%s\"",
|
||||
pd.Subclass.ID,
|
||||
pd.Subclass.Name,
|
||||
),
|
||||
)
|
||||
b.WriteString("},")
|
||||
b.WriteString("\"programming_interface\": {")
|
||||
b.WriteString(
|
||||
fmt.Sprintf(
|
||||
"\"id\":\"%s\",\"name\":\"%s\"",
|
||||
pd.ProgrammingInterface.ID,
|
||||
pd.ProgrammingInterface.Name,
|
||||
),
|
||||
)
|
||||
b.WriteString("}")
|
||||
b.WriteString("}")
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (di *PCIDevice) String() string {
|
||||
vendorName := UNKNOWN
|
||||
if di.Vendor != nil {
|
||||
vendorName = di.Vendor.Name
|
||||
}
|
||||
productName := UNKNOWN
|
||||
if di.Product != nil {
|
||||
productName = di.Product.Name
|
||||
}
|
||||
className := UNKNOWN
|
||||
if di.Class != nil {
|
||||
className = di.Class.Name
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s -> class: '%s' vendor: '%s' product: '%s'",
|
||||
di.Address,
|
||||
className,
|
||||
vendorName,
|
||||
productName,
|
||||
)
|
||||
}
|
||||
|
||||
type PCIInfo struct {
|
||||
ctx *context
|
||||
// hash of class ID -> class information
|
||||
Classes map[string]*pcidb.Class
|
||||
// hash of vendor ID -> vendor information
|
||||
Vendors map[string]*pcidb.Vendor
|
||||
// hash of vendor ID + product/device ID -> product information
|
||||
Products map[string]*pcidb.Product
|
||||
}
|
||||
|
||||
type PCIAddress struct {
|
||||
Domain string
|
||||
Bus string
|
||||
Slot string
|
||||
Function string
|
||||
}
|
||||
|
||||
// Given a string address, returns a complete PCIAddress struct, filled in with
|
||||
// domain, bus, slot and function components. The address string may either
|
||||
// be in $BUS:$SLOT.$FUNCTION (BSF) format or it can be a full PCI address
|
||||
// that includes the 4-digit $DOMAIN information as well:
|
||||
// $DOMAIN:$BUS:$SLOT.$FUNCTION.
|
||||
//
|
||||
// Returns "" if the address string wasn't a valid PCI address.
|
||||
func PCIAddressFromString(address string) *PCIAddress {
|
||||
addrLowered := strings.ToLower(address)
|
||||
matches := regexPCIAddress.FindStringSubmatch(addrLowered)
|
||||
if len(matches) == 6 {
|
||||
dom := "0000"
|
||||
if matches[1] != "" {
|
||||
dom = matches[2]
|
||||
}
|
||||
return &PCIAddress{
|
||||
Domain: dom,
|
||||
Bus: matches[3],
|
||||
Slot: matches[4],
|
||||
Function: matches[5],
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PCI(opts ...*WithOption) (*PCIInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &PCIInfo{}
|
||||
if err := ctx.pciFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jaypipes/pcidb"
|
||||
)
|
||||
|
||||
func (ctx *context) pciFillInfo(info *PCIInfo) error {
|
||||
db, err := pcidb.New(pcidb.WithChroot(ctx.chroot))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.Classes = db.Classes
|
||||
info.Vendors = db.Vendors
|
||||
info.Products = db.Products
|
||||
info.ctx = ctx
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *context) getPCIDeviceModaliasPath(address string) string {
|
||||
pciAddr := PCIAddressFromString(address)
|
||||
if pciAddr == nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(
|
||||
ctx.pathSysBusPciDevices(),
|
||||
pciAddr.Domain+":"+pciAddr.Bus+":"+pciAddr.Slot+"."+pciAddr.Function,
|
||||
"modalias",
|
||||
)
|
||||
}
|
||||
|
||||
type deviceModaliasInfo struct {
|
||||
vendorID string
|
||||
productID string
|
||||
subproductID string
|
||||
subvendorID string
|
||||
classID string
|
||||
subclassID string
|
||||
progIfaceID string
|
||||
}
|
||||
|
||||
func parseModaliasFile(fp string) *deviceModaliasInfo {
|
||||
if _, err := os.Stat(fp); err != nil {
|
||||
return nil
|
||||
}
|
||||
data, err := ioutil.ReadFile(fp)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// The modalias file is an encoded file that looks like this:
|
||||
//
|
||||
// $ cat /sys/devices/pci0000\:00/0000\:00\:03.0/0000\:03\:00.0/modalias
|
||||
// pci:v000010DEd00001C82sv00001043sd00008613bc03sc00i00
|
||||
//
|
||||
// It is interpreted like so:
|
||||
//
|
||||
// pci: -- ignore
|
||||
// v000010DE -- PCI vendor ID
|
||||
// d00001C82 -- PCI device ID (the product/model ID)
|
||||
// sv00001043 -- PCI subsystem vendor ID
|
||||
// sd00008613 -- PCI subsystem device ID (subdevice product/model ID)
|
||||
// bc03 -- PCI base class
|
||||
// sc00 -- PCI subclass
|
||||
// i00 -- programming interface
|
||||
vendorID := strings.ToLower(string(data[9:13]))
|
||||
productID := strings.ToLower(string(data[18:22]))
|
||||
subvendorID := strings.ToLower(string(data[28:32]))
|
||||
subproductID := strings.ToLower(string(data[38:42]))
|
||||
classID := string(data[44:46])
|
||||
subclassID := string(data[48:50])
|
||||
progIfaceID := string(data[51:53])
|
||||
return &deviceModaliasInfo{
|
||||
vendorID: vendorID,
|
||||
productID: productID,
|
||||
subproductID: subproductID,
|
||||
subvendorID: subvendorID,
|
||||
classID: classID,
|
||||
subclassID: subclassID,
|
||||
progIfaceID: progIfaceID,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a pointer to a pcidb.Vendor struct matching the supplied vendor
|
||||
// ID string. If no such vendor ID string could be found, returns the
|
||||
// pcidb.Vendor struct populated with "unknown" vendor Name attribute and
|
||||
// empty Products attribute.
|
||||
func findPCIVendor(info *PCIInfo, vendorID string) *pcidb.Vendor {
|
||||
vendor := info.Vendors[vendorID]
|
||||
if vendor == nil {
|
||||
return &pcidb.Vendor{
|
||||
ID: vendorID,
|
||||
Name: UNKNOWN,
|
||||
Products: []*pcidb.Product{},
|
||||
}
|
||||
}
|
||||
return vendor
|
||||
}
|
||||
|
||||
// Returns a pointer to a pcidb.Product struct matching the supplied vendor
|
||||
// and product ID strings. If no such product could be found, returns the
|
||||
// pcidb.Product struct populated with "unknown" product Name attribute and
|
||||
// empty Subsystems attribute.
|
||||
func findPCIProduct(
|
||||
info *PCIInfo,
|
||||
vendorID string,
|
||||
productID string,
|
||||
) *pcidb.Product {
|
||||
product := info.Products[vendorID+productID]
|
||||
if product == nil {
|
||||
return &pcidb.Product{
|
||||
ID: productID,
|
||||
Name: UNKNOWN,
|
||||
Subsystems: []*pcidb.Product{},
|
||||
}
|
||||
}
|
||||
return product
|
||||
}
|
||||
|
||||
// Returns a pointer to a pcidb.Product struct matching the supplied vendor,
|
||||
// product, subvendor and subproduct ID strings. If no such product could be
|
||||
// found, returns the pcidb.Product struct populated with "unknown" product
|
||||
// Name attribute and empty Subsystems attribute.
|
||||
func findPCISubsystem(
|
||||
info *PCIInfo,
|
||||
vendorID string,
|
||||
productID string,
|
||||
subvendorID string,
|
||||
subproductID string,
|
||||
) *pcidb.Product {
|
||||
product := info.Products[vendorID+productID]
|
||||
subvendor := info.Vendors[subvendorID]
|
||||
if subvendor != nil && product != nil {
|
||||
for _, p := range product.Subsystems {
|
||||
if p.ID == subproductID {
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
return &pcidb.Product{
|
||||
VendorID: subvendorID,
|
||||
ID: subproductID,
|
||||
Name: UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a pointer to a pcidb.Class struct matching the supplied class ID
|
||||
// string. If no such class ID string could be found, returns the
|
||||
// pcidb.Class struct populated with "unknown" class Name attribute and
|
||||
// empty Subclasses attribute.
|
||||
func findPCIClass(info *PCIInfo, classID string) *pcidb.Class {
|
||||
class := info.Classes[classID]
|
||||
if class == nil {
|
||||
return &pcidb.Class{
|
||||
ID: classID,
|
||||
Name: UNKNOWN,
|
||||
Subclasses: []*pcidb.Subclass{},
|
||||
}
|
||||
}
|
||||
return class
|
||||
}
|
||||
|
||||
// Returns a pointer to a pcidb.Subclass struct matching the supplied class
|
||||
// and subclass ID strings. If no such subclass could be found, returns the
|
||||
// pcidb.Subclass struct populated with "unknown" subclass Name attribute
|
||||
// and empty ProgrammingInterfaces attribute.
|
||||
func findPCISubclass(
|
||||
info *PCIInfo,
|
||||
classID string,
|
||||
subclassID string,
|
||||
) *pcidb.Subclass {
|
||||
class := info.Classes[classID]
|
||||
if class != nil {
|
||||
for _, sc := range class.Subclasses {
|
||||
if sc.ID == subclassID {
|
||||
return sc
|
||||
}
|
||||
}
|
||||
}
|
||||
return &pcidb.Subclass{
|
||||
ID: subclassID,
|
||||
Name: UNKNOWN,
|
||||
ProgrammingInterfaces: []*pcidb.ProgrammingInterface{},
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a pointer to a pcidb.ProgrammingInterface struct matching the
|
||||
// supplied class, subclass and programming interface ID strings. If no such
|
||||
// programming interface could be found, returns the
|
||||
// pcidb.ProgrammingInterface struct populated with "unknown" Name attribute
|
||||
func findPCIProgrammingInterface(
|
||||
info *PCIInfo,
|
||||
classID string,
|
||||
subclassID string,
|
||||
progIfaceID string,
|
||||
) *pcidb.ProgrammingInterface {
|
||||
subclass := findPCISubclass(info, classID, subclassID)
|
||||
for _, pi := range subclass.ProgrammingInterfaces {
|
||||
if pi.ID == progIfaceID {
|
||||
return pi
|
||||
}
|
||||
}
|
||||
return &pcidb.ProgrammingInterface{
|
||||
ID: progIfaceID,
|
||||
Name: UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDevice returns a pointer to a PCIDevice struct that describes the PCI
|
||||
// device at the requested address. If no such device could be found, returns
|
||||
// nil
|
||||
func (info *PCIInfo) GetDevice(address string) *PCIDevice {
|
||||
fp := info.ctx.getPCIDeviceModaliasPath(address)
|
||||
if fp == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
modaliasInfo := parseModaliasFile(fp)
|
||||
if modaliasInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
vendor := findPCIVendor(info, modaliasInfo.vendorID)
|
||||
product := findPCIProduct(
|
||||
info,
|
||||
modaliasInfo.vendorID,
|
||||
modaliasInfo.productID,
|
||||
)
|
||||
subsystem := findPCISubsystem(
|
||||
info,
|
||||
modaliasInfo.vendorID,
|
||||
modaliasInfo.productID,
|
||||
modaliasInfo.subvendorID,
|
||||
modaliasInfo.subproductID,
|
||||
)
|
||||
class := findPCIClass(info, modaliasInfo.classID)
|
||||
subclass := findPCISubclass(
|
||||
info,
|
||||
modaliasInfo.classID,
|
||||
modaliasInfo.subclassID,
|
||||
)
|
||||
progIface := findPCIProgrammingInterface(
|
||||
info,
|
||||
modaliasInfo.classID,
|
||||
modaliasInfo.subclassID,
|
||||
modaliasInfo.progIfaceID,
|
||||
)
|
||||
|
||||
return &PCIDevice{
|
||||
Address: address,
|
||||
Vendor: vendor,
|
||||
Subsystem: subsystem,
|
||||
Product: product,
|
||||
Class: class,
|
||||
Subclass: subclass,
|
||||
ProgrammingInterface: progIface,
|
||||
}
|
||||
}
|
||||
|
||||
// ListDevices returns a list of pointers to PCIDevice structs present on the
|
||||
// host system
|
||||
func (info *PCIInfo) ListDevices() []*PCIDevice {
|
||||
devs := make([]*PCIDevice, 0)
|
||||
// We scan the /sys/bus/pci/devices directory which contains a collection
|
||||
// of symlinks. The names of the symlinks are all the known PCI addresses
|
||||
// for the host. For each address, we grab a *PCIDevice matching the
|
||||
// address and append to the returned array.
|
||||
links, err := ioutil.ReadDir(info.ctx.pathSysBusPciDevices())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "error: failed to read /sys/bus/pci/devices")
|
||||
return nil
|
||||
}
|
||||
var dev *PCIDevice
|
||||
for _, link := range links {
|
||||
addr := link.Name()
|
||||
dev = info.GetDevice(addr)
|
||||
if dev == nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "error: failed to get device information for PCI address %s\n", addr)
|
||||
} else {
|
||||
devs = append(devs, dev)
|
||||
}
|
||||
}
|
||||
return devs
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// +build !linux
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) pciFillInfo(info *PCIInfo) error {
|
||||
return errors.New("pciFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
|
||||
// GetDevice returns a pointer to a PCIDevice struct that describes the PCI
|
||||
// device at the requested address. If no such device could be found, returns
|
||||
// nil
|
||||
func (info *PCIInfo) GetDevice(address string) *PCIDevice {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListDevices returns a list of pointers to PCIDevice structs present on the
|
||||
// host system
|
||||
func (info *PCIInfo) ListDevices() []*PCIDevice {
|
||||
return nil
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// 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 "fmt"
|
||||
|
||||
// ProductInfo defines product information
|
||||
type ProductInfo struct {
|
||||
Family string `json:"family"`
|
||||
Name string `json:"name"`
|
||||
Vendor string `json:"vendor"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
UUID string `json:"uuid"`
|
||||
SKU string `json:"sku"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (i *ProductInfo) String() string {
|
||||
familyStr := ""
|
||||
if i.Family != "" {
|
||||
familyStr = " family=" + i.Family
|
||||
}
|
||||
nameStr := ""
|
||||
if i.Name != "" {
|
||||
nameStr = " name=" + i.Name
|
||||
}
|
||||
vendorStr := ""
|
||||
if i.Vendor != "" {
|
||||
vendorStr = " vendor=" + i.Vendor
|
||||
}
|
||||
serialStr := ""
|
||||
if i.SerialNumber != "" && i.SerialNumber != UNKNOWN {
|
||||
serialStr = " serial=" + i.SerialNumber
|
||||
}
|
||||
uuidStr := ""
|
||||
if i.UUID != "" && i.UUID != UNKNOWN {
|
||||
uuidStr = " uuid=" + i.UUID
|
||||
}
|
||||
skuStr := ""
|
||||
if i.SKU != "" {
|
||||
skuStr = " sku=" + i.SKU
|
||||
}
|
||||
versionStr := ""
|
||||
if i.Version != "" {
|
||||
versionStr = " version=" + i.Version
|
||||
}
|
||||
|
||||
res := fmt.Sprintf(
|
||||
"product%s%s%s%s%s%s%s",
|
||||
familyStr,
|
||||
nameStr,
|
||||
vendorStr,
|
||||
serialStr,
|
||||
uuidStr,
|
||||
skuStr,
|
||||
versionStr,
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// Product returns a pointer to a ProductInfo struct containing information
|
||||
// about the host's product
|
||||
func Product(opts ...*WithOption) (*ProductInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &ProductInfo{}
|
||||
if err := ctx.productFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate product information in a top-level
|
||||
// "product" YAML/JSON map/object key
|
||||
type productPrinter struct {
|
||||
Info *ProductInfo `json:"product"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the product information formatted as YAML
|
||||
// under a top-level "dmi:" key
|
||||
func (info *ProductInfo) YAMLString() string {
|
||||
return safeYAML(productPrinter{info})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the product information formatted as JSON
|
||||
// under a top-level "product:" key
|
||||
func (info *ProductInfo) JSONString(indent bool) string {
|
||||
return safeJSON(productPrinter{info}, indent)
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package ghw
|
||||
|
||||
func (ctx *context) productFillInfo(info *ProductInfo) error {
|
||||
|
||||
info.Family = ctx.dmiItem("product_family")
|
||||
info.Name = ctx.dmiItem("product_name")
|
||||
info.Vendor = ctx.dmiItem("sys_vendor")
|
||||
info.SerialNumber = ctx.dmiItem("product_serial")
|
||||
info.UUID = ctx.dmiItem("product_uuid")
|
||||
info.SKU = ctx.dmiItem("product_sku")
|
||||
info.Version = ctx.dmiItem("product_version")
|
||||
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux,!windows
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) productFillInfo(info *ProductInfo) error {
|
||||
return errors.New("productFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// 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 (
|
||||
"github.com/StackExchange/wmi"
|
||||
)
|
||||
|
||||
const wqlProduct = "SELECT Caption, Description, IdentifyingNumber, Name, SKUNumber, Vendor, Version, UUID FROM Win32_ComputerSystemProduct"
|
||||
|
||||
type win32Product struct {
|
||||
Caption *string
|
||||
Description *string
|
||||
IdentifyingNumber *string
|
||||
Name *string
|
||||
SKUNumber *string
|
||||
Vendor *string
|
||||
Version *string
|
||||
UUID *string
|
||||
}
|
||||
|
||||
func (ctx *context) productFillInfo(info *ProductInfo) error {
|
||||
// Getting data from WMI
|
||||
var win32ProductDescriptions []win32Product
|
||||
// Assuming the first product is the host...
|
||||
if err := wmi.Query(wqlProduct, &win32ProductDescriptions); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(win32ProductDescriptions) > 0 {
|
||||
info.Family = UNKNOWN
|
||||
info.Name = *win32ProductDescriptions[0].Name
|
||||
info.Vendor = *win32ProductDescriptions[0].Vendor
|
||||
info.SerialNumber = *win32ProductDescriptions[0].IdentifyingNumber
|
||||
info.UUID = *win32ProductDescriptions[0].UUID
|
||||
info.SKU = *win32ProductDescriptions[0].SKUNumber
|
||||
info.Version = *win32ProductDescriptions[0].Version
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// TopologyNode is an abstract construct representing a collection of
|
||||
// processors and various levels of memory cache that those processors share.
|
||||
// In a NUMA architecture, there are multiple NUMA nodes, abstracted here as
|
||||
// multiple TopologyNode structs. In an SMP architecture, a single TopologyNode
|
||||
// will be available in the TopologyInfo struct and this single struct can be
|
||||
// used to describe the levels of memory caching available to the single
|
||||
// physical processor package's physical processor cores
|
||||
type TopologyNode struct {
|
||||
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
|
||||
Id int `json:"-"`
|
||||
ID int `json:"id"`
|
||||
Cores []*ProcessorCore `json:"cores"`
|
||||
Caches []*MemoryCache `json:"caches"`
|
||||
}
|
||||
|
||||
func (n *TopologyNode) String() string {
|
||||
return fmt.Sprintf(
|
||||
"node #%d (%d cores)",
|
||||
n.ID,
|
||||
len(n.Cores),
|
||||
)
|
||||
}
|
||||
|
||||
// TopologyInfo describes the system topology for the host hardware
|
||||
type TopologyInfo struct {
|
||||
Architecture Architecture `json:"architecture"`
|
||||
Nodes []*TopologyNode `json:"nodes"`
|
||||
}
|
||||
|
||||
// Topology returns a TopologyInfo struct that describes the system topology of
|
||||
// the host hardware
|
||||
func Topology(opts ...*WithOption) (*TopologyInfo, error) {
|
||||
mergeOpts := mergeOptions(opts...)
|
||||
ctx := &context{
|
||||
chroot: *mergeOpts.Chroot,
|
||||
}
|
||||
info := &TopologyInfo{}
|
||||
if err := ctx.topologyFillInfo(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, node := range info.Nodes {
|
||||
sort.Sort(SortByMemoryCacheLevelTypeFirstProcessor(node.Caches))
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (i *TopologyInfo) String() string {
|
||||
archStr := "SMP"
|
||||
if i.Architecture == ARCHITECTURE_NUMA {
|
||||
archStr = "NUMA"
|
||||
}
|
||||
res := fmt.Sprintf(
|
||||
"topology %s (%d nodes)",
|
||||
archStr,
|
||||
len(i.Nodes),
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// simple private struct used to encapsulate topology information in a
|
||||
// top-level "topology" YAML/JSON map/object key
|
||||
type topologyPrinter struct {
|
||||
Info *TopologyInfo `json:"topology"`
|
||||
}
|
||||
|
||||
// YAMLString returns a string with the topology information formatted as YAML
|
||||
// under a top-level "topology:" key
|
||||
func (i *TopologyInfo) YAMLString() string {
|
||||
return safeYAML(topologyPrinter{i})
|
||||
}
|
||||
|
||||
// JSONString returns a string with the topology information formatted as JSON
|
||||
// under a top-level "topology:" key
|
||||
func (i *TopologyInfo) JSONString(indent bool) string {
|
||||
return safeJSON(topologyPrinter{i}, indent)
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// 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 (
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (ctx *context) topologyFillInfo(info *TopologyInfo) error {
|
||||
info.Nodes = ctx.topologyNodes()
|
||||
if len(info.Nodes) == 1 {
|
||||
info.Architecture = ARCHITECTURE_SMP
|
||||
} else {
|
||||
info.Architecture = ARCHITECTURE_NUMA
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TopologyNodes has been deprecated in 0.2. Please use the TopologyInfo.Nodes
|
||||
// attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func TopologyNodes() ([]*TopologyNode, error) {
|
||||
msg := `
|
||||
The TopologyNodes() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the TopologyInfo.Nodes attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.topologyNodes(), nil
|
||||
}
|
||||
|
||||
func (ctx *context) topologyNodes() []*TopologyNode {
|
||||
nodes := make([]*TopologyNode, 0)
|
||||
|
||||
files, err := ioutil.ReadDir(ctx.pathSysDevicesSystemNode())
|
||||
if err != nil {
|
||||
warn("failed to determine nodes: %s\n", err)
|
||||
return nodes
|
||||
}
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !strings.HasPrefix(filename, "node") {
|
||||
continue
|
||||
}
|
||||
node := &TopologyNode{}
|
||||
nodeID, err := strconv.Atoi(filename[4:])
|
||||
if err != nil {
|
||||
warn("failed to determine node ID: %s\n", err)
|
||||
return nodes
|
||||
}
|
||||
node.ID = nodeID
|
||||
cores, err := ctx.coresForNode(nodeID)
|
||||
if err != nil {
|
||||
warn("failed to determine cores for node: %s\n", err)
|
||||
return nodes
|
||||
}
|
||||
node.Cores = cores
|
||||
caches, err := ctx.cachesForNode(nodeID)
|
||||
if err != nil {
|
||||
warn("failed to determine caches for node: %s\n", err)
|
||||
return nodes
|
||||
}
|
||||
node.Caches = caches
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// +build !linux
|
||||
// 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 (
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ctx *context) topologyFillInfo(info *TopologyInfo) error {
|
||||
return errors.New("topologyFillInfo not implemented on " + runtime.GOOS)
|
||||
}
|
||||
|
||||
// TopologyNodes has been deprecated in 0.2. Please use the TopologyInfo.Nodes
|
||||
// attribute.
|
||||
// TODO(jaypipes): Remove in 1.0.
|
||||
func TopologyNodes() ([]*TopologyNode, error) {
|
||||
msg := `
|
||||
The TopologyNodes() function has been DEPRECATED and will be removed in the 1.0
|
||||
release of ghw. Please use the TopologyInfo.Nodes attribute.
|
||||
`
|
||||
warn(msg)
|
||||
ctx := contextFromEnv()
|
||||
return ctx.topologyNodes()
|
||||
}
|
||||
|
||||
func (ctx *context) topologyNodes() ([]*TopologyNode, error) {
|
||||
return nil, errors.New("Don't know how to get topology on " + runtime.GOOS)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package ghw
|
||||
|
||||
var (
|
||||
KB int64 = 1024
|
||||
MB = KB * 1024
|
||||
GB = MB * 1024
|
||||
TB = GB * 1024
|
||||
PB = TB * 1024
|
||||
EB = PB * 1024
|
||||
)
|
||||
|
||||
func unitWithString(size int64) (int64, string) {
|
||||
switch {
|
||||
case size < MB:
|
||||
return KB, "KB"
|
||||
case size < GB:
|
||||
return MB, "MB"
|
||||
case size < TB:
|
||||
return GB, "GB"
|
||||
case size < PB:
|
||||
return TB, "TB"
|
||||
case size < EB:
|
||||
return PB, "PB"
|
||||
default:
|
||||
return EB, "EB"
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
disableWarningsEnv = "GHW_DISABLE_WARNINGS"
|
||||
)
|
||||
|
||||
type closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
func safeClose(c closer) {
|
||||
err := c.Close()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "failed to close: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func warn(msg string, args ...interface{}) {
|
||||
if _, ok := os.LookupEnv(disableWarningsEnv); ok {
|
||||
return
|
||||
}
|
||||
_, _ = fmt.Fprint(os.Stderr, "WARNING: ")
|
||||
_, _ = fmt.Fprintf(os.Stderr, msg, args...)
|
||||
}
|
||||
|
||||
// Reads a supplied filepath and converts the contents to an integer. Returns
|
||||
// -1 if there were file permissions or existence errors or if the contents
|
||||
// could not be successfully converted to an integer. In any error, a warning
|
||||
// message is printed to STDERR and -1 is returned.
|
||||
func safeIntFromFile(path string) int {
|
||||
msg := "failed to read int from file: %s\n"
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
warn(msg, err)
|
||||
return -1
|
||||
}
|
||||
contents := strings.TrimSpace(string(buf))
|
||||
res, err := strconv.Atoi(contents)
|
||||
if err != nil {
|
||||
warn(msg, err)
|
||||
return -1
|
||||
}
|
||||
return res
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#! /bin/sh
|
||||
|
||||
export GO_PACKAGES=$(go list ./... | grep -v /vendor/)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
vendor/
|
||||
coverage*.*
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
language: go
|
||||
script:
|
||||
- source ./.get-go-packages.sh
|
||||
- go test -v $GO_PACKAGES
|
||||
matrix:
|
||||
include:
|
||||
# On Go 1.10 and Go 1.11, use dep to ensure dependencies before running go
|
||||
# test.
|
||||
- os: windows
|
||||
go: "1.10"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
- os: linux
|
||||
go: "1.10"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
- os: osx
|
||||
go: "1.10"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
- os: windows
|
||||
go: "1.11"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
- os: linux
|
||||
go: "1.11"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
- os: osx
|
||||
go: "1.11"
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure -v
|
||||
# On Go 1.12, use go modules to ensure dependencies instead of dep
|
||||
- os: windows
|
||||
go: "1.12"
|
||||
env: GO111MODULE=on
|
||||
- os: linux
|
||||
go: "1.12"
|
||||
env: GO111MODULE=on
|
||||
- os: osx
|
||||
go: "1.12"
|
||||
env: GO111MODULE=on
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:78bbb1ba5b7c3f2ed0ea1eab57bdd3859aec7e177811563edc41198a760b06af"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4"
|
||||
version = "v1.0.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = ["github.com/mitchellh/go-homedir"]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
version = "1.0.0"
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
VENDOR := vendor
|
||||
PKGS := $(shell go list ./... | grep -v /$(VENDOR)/)
|
||||
SRC = $(shell find . -type f -name '*.go' -not -path "*/$(VENDOR)/*")
|
||||
BIN_DIR := $(GOPATH)/bin
|
||||
DEP := $(BIN_DIR)/dep
|
||||
GOMETALINTER := $(BIN_DIR)/gometalinter
|
||||
|
||||
.PHONY: test
|
||||
test: vet
|
||||
go test $(PKGS)
|
||||
|
||||
$(DEP):
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
|
||||
.PHONY: dep
|
||||
dep: $(DEP)
|
||||
$(DEP) ensure
|
||||
|
||||
$(GOMETALINTER):
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
$(GOMETALINTER) --install &> /dev/null
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GOMETALINTER)
|
||||
$(GOMETALINTER) ./... --vendor
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@gofmt -s -l -w $(SRC)
|
||||
|
||||
.PHONY: fmtcheck
|
||||
fmtcheck:
|
||||
@bash -c "diff -u <(echo -n) <(gofmt -d $(SRC))"
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet $(PKGS)
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
$(shell [ -e coverage.out ] && rm coverage.out)
|
||||
@echo "mode: count" > coverage-all.out
|
||||
@$(foreach pkg,$(PKGS),\
|
||||
go test -coverprofile=coverage.out -covermode=count $(pkg);\
|
||||
tail -n +2 coverage.out >> coverage-all.out;)
|
||||
go tool cover -html=coverage-all.out -o=coverage-all.html
|
||||
+413
@@ -0,0 +1,413 @@
|
||||
# `pcidb` - the Golang PCI DB library [](https://travis-ci.org/jaypipes/pcidb)
|
||||
|
||||
`pcidb` is a small Golang library for programmatic querying of PCI vendor,
|
||||
product and class information.
|
||||
|
||||
We currently [test](https://travis-ci.org/jaypipes/pcidb/) `pcidb` on Linux, Windows and MacOSX.
|
||||
|
||||
## Usage
|
||||
|
||||
`pcidb` contains a PCI database inspection and querying facility that allows
|
||||
developers to query for information about hardware device classes, vendor and
|
||||
product information.
|
||||
|
||||
The `pcidb.New()` function returns a `pcidb.PCIDB` struct or an error if the
|
||||
PCI database could not be loaded.
|
||||
|
||||
> `pcidb`'s default behaviour is to first search for pci-ids DB files on the
|
||||
> local host system in well-known filesystem paths. If `pcidb` cannot find a
|
||||
> pci-ids DB file on the local host system, it will then fetch a current
|
||||
> pci-ids DB file from the network. You can disable this network-fetching
|
||||
> behaviour with the `pcidb.WithDisableNetworkFetch()` function or set the
|
||||
> `PCIDB_DISABLE_NETWORK_FETCH` to a non-0 value.
|
||||
|
||||
The `pcidb.PCIDB` struct contains a number of fields that may be queried for
|
||||
PCI information:
|
||||
|
||||
* `pcidb.PCIDB.Classes` is a map, keyed by the PCI class ID (a hex-encoded
|
||||
string) of pointers to `pcidb.Class` structs, one for each class of PCI
|
||||
device known to `pcidb`
|
||||
* `pcidb.PCIDB.Vendors` is a map, keyed by the PCI vendor ID (a hex-encoded
|
||||
string) of pointers to `pcidb.Vendor` structs, one for each PCI vendor
|
||||
known to `pcidb`
|
||||
* `pcidb.PCIDB.Products` is a map, keyed by the PCI product ID* (a hex-encoded
|
||||
string) of pointers to `pcidb.Product` structs, one for each PCI product
|
||||
known to `pcidb`
|
||||
|
||||
**NOTE**: PCI products are often referred to by their "device ID". We use
|
||||
the term "product ID" in `pcidb` because it more accurately reflects what the
|
||||
identifier is for: a specific product line produced by the vendor.
|
||||
|
||||
### Overriding the root mountpoint `pcidb` uses
|
||||
|
||||
The default root mountpoint that `pcidb` uses when looking for information
|
||||
about the host system is `/`. So, for example, when looking up known PCI IDS DB
|
||||
files on Linux, `pcidb` will attempt to discover a pciids DB file at
|
||||
`/usr/share/misc/pci.ids`. If you are calling `pcidb` from a system that has an
|
||||
alternate root mountpoint, you can either set the `PCIDB_CHROOT` environment
|
||||
variable to that alternate path, or call the `pcidb.New()` function with the
|
||||
`pcidb.WithChroot()` modifier.
|
||||
|
||||
For example, if you are executing from within an application container that has
|
||||
bind-mounted the root host filesystem to the mount point `/host`, you would set
|
||||
`PCIDB_CHROOT` to `/host` so that pcidb can find files like
|
||||
`/usr/share/misc/pci.ids` at `/host/usr/share/misc/pci.ids`.
|
||||
|
||||
Alternately, you can use the `pcidb.WithChroot()` function like so:
|
||||
|
||||
```go
|
||||
pci := pcidb.New(pcidb.WithChroot("/host"))
|
||||
```
|
||||
|
||||
### PCI device classes
|
||||
|
||||
Let's take a look at the PCI device class information and how to query the PCI
|
||||
database for class, subclass, and programming interface information.
|
||||
|
||||
Each `pcidb.Class` struct contains the following fields:
|
||||
|
||||
* `pcidb.Class.ID` is the hex-encoded string identifier for the device
|
||||
class
|
||||
* `pcidb.Class.Name` is the common name/description of the class
|
||||
* `pcidb.Class.Subclasses` is an array of pointers to
|
||||
`pcidb.Subclass` structs, one for each subclass in the device class
|
||||
|
||||
Each `pcidb.Subclass` struct contains the following fields:
|
||||
|
||||
* `pcidb.Subclass.ID` is the hex-encoded string identifier for the device
|
||||
subclass
|
||||
* `pcidb.Subclass.Name` is the common name/description of the subclass
|
||||
* `pcidb.Subclass.ProgrammingInterfaces` is an array of pointers to
|
||||
`pcidb.ProgrammingInterface` structs, one for each programming interface
|
||||
for the device subclass
|
||||
|
||||
Each `pcidb.ProgrammingInterface` struct contains the following fields:
|
||||
|
||||
* `pcidb.ProgrammingInterface.ID` is the hex-encoded string identifier for
|
||||
the programming interface
|
||||
* `pcidb.ProgrammingInterface.Name` is the common name/description for the
|
||||
programming interface
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaypipes/pcidb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pci, err := pcidb.New()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting PCI info: %v", err)
|
||||
}
|
||||
|
||||
for _, devClass := range pci.Classes {
|
||||
fmt.Printf(" Device class: %v ('%v')\n", devClass.Name, devClass.ID)
|
||||
for _, devSubclass := range devClass.Subclasses {
|
||||
fmt.Printf(" Device subclass: %v ('%v')\n", devSubclass.Name, devSubclass.ID)
|
||||
for _, progIface := range devSubclass.ProgrammingInterfaces {
|
||||
fmt.Printf(" Programming interface: %v ('%v')\n", progIface.Name, progIface.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example output from my personal workstation, snipped for brevity:
|
||||
|
||||
```
|
||||
...
|
||||
Device class: Serial bus controller ('0c')
|
||||
Device subclass: FireWire (IEEE 1394) ('00')
|
||||
Programming interface: Generic ('00')
|
||||
Programming interface: OHCI ('10')
|
||||
Device subclass: ACCESS Bus ('01')
|
||||
Device subclass: SSA ('02')
|
||||
Device subclass: USB controller ('03')
|
||||
Programming interface: UHCI ('00')
|
||||
Programming interface: OHCI ('10')
|
||||
Programming interface: EHCI ('20')
|
||||
Programming interface: XHCI ('30')
|
||||
Programming interface: Unspecified ('80')
|
||||
Programming interface: USB Device ('fe')
|
||||
Device subclass: Fibre Channel ('04')
|
||||
Device subclass: SMBus ('05')
|
||||
Device subclass: InfiniBand ('06')
|
||||
Device subclass: IPMI SMIC interface ('07')
|
||||
Device subclass: SERCOS interface ('08')
|
||||
Device subclass: CANBUS ('09')
|
||||
...
|
||||
```
|
||||
|
||||
### PCI vendors and products
|
||||
|
||||
Let's take a look at the PCI vendor information and how to query the PCI
|
||||
database for vendor information and the products a vendor supplies.
|
||||
|
||||
Each `pcidb.Vendor` struct contains the following fields:
|
||||
|
||||
* `pcidb.Vendor.ID` is the hex-encoded string identifier for the vendor
|
||||
* `pcidb.Vendor.Name` is the common name/description of the vendor
|
||||
* `pcidb.Vendor.Products` is an array of pointers to `pcidb.Product`
|
||||
structs, one for each product supplied by the vendor
|
||||
|
||||
Each `pcidb.Product` struct contains the following fields:
|
||||
|
||||
* `pcidb.Product.VendorID` is the hex-encoded string identifier for the
|
||||
product's vendor
|
||||
* `pcidb.Product.ID` is the hex-encoded string identifier for the product
|
||||
* `pcidb.Product.Name` is the common name/description of the subclass
|
||||
* `pcidb.Product.Subsystems` is an array of pointers to
|
||||
`pcidb.Product` structs, one for each "subsystem" (sometimes called
|
||||
"sub-device" in PCI literature) for the product
|
||||
|
||||
**NOTE**: A subsystem product may have a different vendor than its "parent" PCI
|
||||
product. This is sometimes referred to as the "sub-vendor".
|
||||
|
||||
Here's some example code that demonstrates listing the PCI vendors with the
|
||||
most known products:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/jaypipes/pcidb"
|
||||
)
|
||||
|
||||
type ByCountProducts []*pcidb.Vendor
|
||||
|
||||
func (v ByCountProducts) Len() int {
|
||||
return len(v)
|
||||
}
|
||||
|
||||
func (v ByCountProducts) Swap(i, j int) {
|
||||
v[i], v[j] = v[j], v[i]
|
||||
}
|
||||
|
||||
func (v ByCountProducts) Less(i, j int) bool {
|
||||
return len(v[i].Products) > len(v[j].Products)
|
||||
}
|
||||
|
||||
func main() {
|
||||
pci, err := pcidb.New()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting PCI info: %v", err)
|
||||
}
|
||||
|
||||
vendors := make([]*pcidb.Vendor, len(pci.Vendors))
|
||||
x := 0
|
||||
for _, vendor := range pci.Vendors {
|
||||
vendors[x] = vendor
|
||||
x++
|
||||
}
|
||||
|
||||
sort.Sort(ByCountProducts(vendors))
|
||||
|
||||
fmt.Println("Top 5 vendors by product")
|
||||
fmt.Println("====================================================")
|
||||
for _, vendor := range vendors[0:5] {
|
||||
fmt.Printf("%v ('%v') has %d products\n", vendor.Name, vendor.ID, len(vendor.Products))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
which yields (on my local workstation as of July 7th, 2018):
|
||||
|
||||
```
|
||||
Top 5 vendors by product
|
||||
====================================================
|
||||
Intel Corporation ('8086') has 3389 products
|
||||
NVIDIA Corporation ('10de') has 1358 products
|
||||
Advanced Micro Devices, Inc. [AMD/ATI] ('1002') has 886 products
|
||||
National Instruments ('1093') has 601 products
|
||||
Chelsio Communications Inc ('1425') has 525 products
|
||||
```
|
||||
|
||||
The following is an example of querying the PCI product and subsystem
|
||||
information to find the products which have the most number of subsystems that
|
||||
have a different vendor than the top-level product. In other words, the two
|
||||
products which have been re-sold or re-manufactured with the most number of
|
||||
different companies.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/jaypipes/pcidb"
|
||||
)
|
||||
|
||||
type ByCountSeparateSubvendors []*pcidb.Product
|
||||
|
||||
func (v ByCountSeparateSubvendors) Len() int {
|
||||
return len(v)
|
||||
}
|
||||
|
||||
func (v ByCountSeparateSubvendors) Swap(i, j int) {
|
||||
v[i], v[j] = v[j], v[i]
|
||||
}
|
||||
|
||||
func (v ByCountSeparateSubvendors) Less(i, j int) bool {
|
||||
iVendor := v[i].VendorID
|
||||
iSetSubvendors := make(map[string]bool, 0)
|
||||
iNumDiffSubvendors := 0
|
||||
jVendor := v[j].VendorID
|
||||
jSetSubvendors := make(map[string]bool, 0)
|
||||
jNumDiffSubvendors := 0
|
||||
|
||||
for _, sub := range v[i].Subsystems {
|
||||
if sub.VendorID != iVendor {
|
||||
iSetSubvendors[sub.VendorID] = true
|
||||
}
|
||||
}
|
||||
iNumDiffSubvendors = len(iSetSubvendors)
|
||||
|
||||
for _, sub := range v[j].Subsystems {
|
||||
if sub.VendorID != jVendor {
|
||||
jSetSubvendors[sub.VendorID] = true
|
||||
}
|
||||
}
|
||||
jNumDiffSubvendors = len(jSetSubvendors)
|
||||
|
||||
return iNumDiffSubvendors > jNumDiffSubvendors
|
||||
}
|
||||
|
||||
func main() {
|
||||
pci, err := pcidb.New()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting PCI info: %v", err)
|
||||
}
|
||||
|
||||
products := make([]*pcidb.Product, len(pci.Products))
|
||||
x := 0
|
||||
for _, product := range pci.Products {
|
||||
products[x] = product
|
||||
x++
|
||||
}
|
||||
|
||||
sort.Sort(ByCountSeparateSubvendors(products))
|
||||
|
||||
fmt.Println("Top 2 products by # different subvendors")
|
||||
fmt.Println("====================================================")
|
||||
for _, product := range products[0:2] {
|
||||
vendorID := product.VendorID
|
||||
vendor := pci.Vendors[vendorID]
|
||||
setSubvendors := make(map[string]bool, 0)
|
||||
|
||||
for _, sub := range product.Subsystems {
|
||||
if sub.VendorID != vendorID {
|
||||
setSubvendors[sub.VendorID] = true
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v ('%v') from %v\n", product.Name, product.ID, vendor.Name)
|
||||
fmt.Printf(" -> %d subsystems under the following different vendors:\n", len(setSubvendors))
|
||||
for subvendorID, _ := range setSubvendors {
|
||||
subvendor, exists := pci.Vendors[subvendorID]
|
||||
subvendorName := "Unknown subvendor"
|
||||
if exists {
|
||||
subvendorName = subvendor.Name
|
||||
}
|
||||
fmt.Printf(" - %v ('%v')\n", subvendorName, subvendorID)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
which yields (on my local workstation as of July 7th, 2018):
|
||||
|
||||
```
|
||||
Top 2 products by # different subvendors
|
||||
====================================================
|
||||
RTL-8100/8101L/8139 PCI Fast Ethernet Adapter ('8139') from Realtek Semiconductor Co., Ltd.
|
||||
-> 34 subsystems under the following different vendors:
|
||||
- OVISLINK Corp. ('149c')
|
||||
- EPoX Computer Co., Ltd. ('1695')
|
||||
- Red Hat, Inc ('1af4')
|
||||
- Mitac ('1071')
|
||||
- Netgear ('1385')
|
||||
- Micro-Star International Co., Ltd. [MSI] ('1462')
|
||||
- Hangzhou Silan Microelectronics Co., Ltd. ('1904')
|
||||
- Compex ('11f6')
|
||||
- Edimax Computer Co. ('1432')
|
||||
- KYE Systems Corporation ('1489')
|
||||
- ZyXEL Communications Corporation ('187e')
|
||||
- Acer Incorporated [ALI] ('1025')
|
||||
- Matsushita Electric Industrial Co., Ltd. ('10f7')
|
||||
- Ruby Tech Corp. ('146c')
|
||||
- Belkin ('1799')
|
||||
- Allied Telesis ('1259')
|
||||
- Unex Technology Corp. ('1429')
|
||||
- CIS Technology Inc ('1436')
|
||||
- D-Link System Inc ('1186')
|
||||
- Ambicom Inc ('1395')
|
||||
- AOPEN Inc. ('a0a0')
|
||||
- TTTech Computertechnik AG (Wrong ID) ('0357')
|
||||
- Gigabyte Technology Co., Ltd ('1458')
|
||||
- Packard Bell B.V. ('1631')
|
||||
- Billionton Systems Inc ('14cb')
|
||||
- Kingston Technologies ('2646')
|
||||
- Accton Technology Corporation ('1113')
|
||||
- Samsung Electronics Co Ltd ('144d')
|
||||
- Biostar Microtech Int'l Corp ('1565')
|
||||
- U.S. Robotics ('16ec')
|
||||
- KTI ('8e2e')
|
||||
- Hewlett-Packard Company ('103c')
|
||||
- ASUSTeK Computer Inc. ('1043')
|
||||
- Surecom Technology ('10bd')
|
||||
Bt878 Video Capture ('036e') from Brooktree Corporation
|
||||
-> 30 subsystems under the following different vendors:
|
||||
- iTuner ('aa00')
|
||||
- Nebula Electronics Ltd. ('0071')
|
||||
- DViCO Corporation ('18ac')
|
||||
- iTuner ('aa05')
|
||||
- iTuner ('aa0d')
|
||||
- LeadTek Research Inc. ('107d')
|
||||
- Avermedia Technologies Inc ('1461')
|
||||
- Chaintech Computer Co. Ltd ('270f')
|
||||
- iTuner ('aa07')
|
||||
- iTuner ('aa0a')
|
||||
- Microtune, Inc. ('1851')
|
||||
- iTuner ('aa01')
|
||||
- iTuner ('aa04')
|
||||
- iTuner ('aa06')
|
||||
- iTuner ('aa0f')
|
||||
- iTuner ('aa02')
|
||||
- iTuner ('aa0b')
|
||||
- Pinnacle Systems, Inc. (Wrong ID) ('bd11')
|
||||
- Rockwell International ('127a')
|
||||
- Askey Computer Corp. ('144f')
|
||||
- Twinhan Technology Co. Ltd ('1822')
|
||||
- Anritsu Corp. ('1852')
|
||||
- iTuner ('aa08')
|
||||
- Hauppauge computer works Inc. ('0070')
|
||||
- Pinnacle Systems Inc. ('11bd')
|
||||
- Conexant Systems, Inc. ('14f1')
|
||||
- iTuner ('aa09')
|
||||
- iTuner ('aa03')
|
||||
- iTuner ('aa0c')
|
||||
- iTuner ('aa0e')
|
||||
```
|
||||
|
||||
## Developers
|
||||
|
||||
Contributions to `pcidb` are welcomed! Fork the repo on GitHub and submit a pull
|
||||
request with your proposed changes. Or, feel free to log an issue for a feature
|
||||
request or bug report.
|
||||
|
||||
### Running tests
|
||||
|
||||
You can run unit tests easily using the `make test` command, like so:
|
||||
|
||||
|
||||
```
|
||||
[jaypipes@uberbox pcidb]$ make test
|
||||
go test github.com/jaypipes/pcidb
|
||||
ok github.com/jaypipes/pcidb 0.045s
|
||||
```
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package pcidb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Concrete merged set of configuration switches that get passed to pcidb
|
||||
// internal functions
|
||||
type context struct {
|
||||
chroot string
|
||||
cacheOnly bool
|
||||
cachePath string
|
||||
disableNetworkFetch bool
|
||||
searchPaths []string
|
||||
}
|
||||
|
||||
func contextFromOptions(merged *WithOption) *context {
|
||||
ctx := &context{
|
||||
chroot: *merged.Chroot,
|
||||
cacheOnly: *merged.CacheOnly,
|
||||
cachePath: getCachePath(),
|
||||
disableNetworkFetch: *merged.DisableNetworkFetch,
|
||||
searchPaths: make([]string, 0),
|
||||
}
|
||||
ctx.setSearchPaths()
|
||||
return ctx
|
||||
}
|
||||
|
||||
func getCachePath() string {
|
||||
hdir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed getting homedir.Dir(): %v", err)
|
||||
return ""
|
||||
}
|
||||
fp, err := homedir.Expand(filepath.Join(hdir, ".cache", "pci.ids"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed expanding local cache path: %v", err)
|
||||
return ""
|
||||
}
|
||||
return fp
|
||||
}
|
||||
|
||||
// Depending on the operating system, sets the context's searchPaths to a set
|
||||
// of local filepaths to search for a pci.ids database file
|
||||
func (ctx *context) setSearchPaths() {
|
||||
// A set of filepaths we will first try to search for the pci-ids DB file
|
||||
// on the local machine. If we fail to find one, we'll try pulling the
|
||||
// latest pci-ids file from the network
|
||||
ctx.searchPaths = append(ctx.searchPaths, ctx.cachePath)
|
||||
if ctx.cacheOnly {
|
||||
return
|
||||
}
|
||||
|
||||
rootPath := ctx.chroot
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
ctx.searchPaths = append(
|
||||
ctx.searchPaths,
|
||||
filepath.Join(rootPath, "usr", "share", "hwdata", "pci.ids"),
|
||||
)
|
||||
ctx.searchPaths = append(
|
||||
ctx.searchPaths,
|
||||
filepath.Join(rootPath, "usr", "share", "misc", "pci.ids"),
|
||||
)
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package pcidb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
PCIIDS_URI = "https://pci-ids.ucw.cz/v2.2/pci.ids.gz"
|
||||
)
|
||||
|
||||
func (db *PCIDB) load(ctx *context) error {
|
||||
var foundPath string
|
||||
for _, fp := range ctx.searchPaths {
|
||||
if _, err := os.Stat(fp); err == nil {
|
||||
foundPath = fp
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundPath == "" {
|
||||
if ctx.disableNetworkFetch {
|
||||
return ERR_NO_DB
|
||||
}
|
||||
// OK, so we didn't find any host-local copy of the pci-ids DB file. Let's
|
||||
// try fetching it from the network and storing it
|
||||
if err := cacheDBFile(ctx.cachePath); err != nil {
|
||||
return err
|
||||
}
|
||||
foundPath = ctx.cachePath
|
||||
}
|
||||
f, err := os.Open(foundPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
return parseDBFile(db, scanner)
|
||||
}
|
||||
|
||||
func ensureDir(fp string) error {
|
||||
fpDir := filepath.Dir(fp)
|
||||
if _, err := os.Stat(fpDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(fpDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pulls down the latest copy of the pci-ids file from the network and stores
|
||||
// it in the local host filesystem
|
||||
func cacheDBFile(cacheFilePath string) error {
|
||||
ensureDir(cacheFilePath)
|
||||
|
||||
response, err := http.Get(PCIIDS_URI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
f, err := os.Create(cacheFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
// write the gunzipped contents to our local cache file
|
||||
zr, err := gzip.NewReader(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zr.Close()
|
||||
if _, err = io.Copy(f, zr); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module github.com/jaypipes/pcidb
|
||||
|
||||
go 1.11
|
||||
|
||||
require github.com/mitchellh/go-homedir v1.0.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package pcidb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
ERR_NO_DB = fmt.Errorf("No pci-ids DB files found (and network fetch disabled)")
|
||||
trueVar = true
|
||||
)
|
||||
|
||||
// ProgrammingInterface is the PCI programming interface for a class of PCI
|
||||
// devices
|
||||
type ProgrammingInterface struct {
|
||||
// hex-encoded PCI_ID of the programming interface
|
||||
ID string `json:"id"`
|
||||
// common string name for the programming interface
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Subclass is a subdivision of a PCI class
|
||||
type Subclass struct {
|
||||
// hex-encoded PCI_ID for the device subclass
|
||||
ID string `json:"id"`
|
||||
// common string name for the subclass
|
||||
Name string `json:"name"`
|
||||
// any programming interfaces this subclass might have
|
||||
ProgrammingInterfaces []*ProgrammingInterface `json:"programming_interfaces"`
|
||||
}
|
||||
|
||||
// Class is the PCI class
|
||||
type Class struct {
|
||||
// hex-encoded PCI_ID for the device class
|
||||
ID string `json:"id"`
|
||||
// common string name for the class
|
||||
Name string `json:"name"`
|
||||
// any subclasses belonging to this class
|
||||
Subclasses []*Subclass `json:"subclasses"`
|
||||
}
|
||||
|
||||
// Product provides information about a PCI device model
|
||||
// NOTE(jaypipes): In the hardware world, the PCI "device_id" is the identifier
|
||||
// for the product/model
|
||||
type Product struct {
|
||||
// vendor ID for the product
|
||||
VendorID string `json:"vendor_id"`
|
||||
// hex-encoded PCI_ID for the product/model
|
||||
ID string `json:"id"`
|
||||
// common string name of the vendor
|
||||
Name string `json:"name"`
|
||||
// "subdevices" or "subsystems" for the product
|
||||
Subsystems []*Product `json:"subsystems"`
|
||||
}
|
||||
|
||||
// Vendor provides information about a device vendor
|
||||
type Vendor struct {
|
||||
// hex-encoded PCI_ID for the vendor
|
||||
ID string `json:"id"`
|
||||
// common string name of the vendor
|
||||
Name string `json:"name"`
|
||||
// all top-level devices for the vendor
|
||||
Products []*Product `json:"products"`
|
||||
}
|
||||
|
||||
type PCIDB struct {
|
||||
// hash of class ID -> class information
|
||||
Classes map[string]*Class `json:"classes"`
|
||||
// hash of vendor ID -> vendor information
|
||||
Vendors map[string]*Vendor `json:"vendors"`
|
||||
// hash of vendor ID + product/device ID -> product information
|
||||
Products map[string]*Product `json:"products"`
|
||||
}
|
||||
|
||||
// WithOption is used to represent optionally-configured settings
|
||||
type WithOption struct {
|
||||
// Chroot is the directory that pcidb uses when attempting to discover
|
||||
// pciids DB files
|
||||
Chroot *string
|
||||
// CacheOnly is mostly just useful for testing. It essentially disables
|
||||
// looking for any non ~/.cache/pci.ids filepaths (which is useful when we
|
||||
// want to test the fetch-from-network code paths
|
||||
CacheOnly *bool
|
||||
// Disables the default behaviour of fetching a pci-ids from a known
|
||||
// location on the network if no local pci-ids DB files can be found.
|
||||
// Useful for secure environments or environments with no network
|
||||
// connectivity.
|
||||
DisableNetworkFetch *bool
|
||||
}
|
||||
|
||||
func WithChroot(dir string) *WithOption {
|
||||
return &WithOption{Chroot: &dir}
|
||||
}
|
||||
|
||||
func WithCacheOnly() *WithOption {
|
||||
return &WithOption{CacheOnly: &trueVar}
|
||||
}
|
||||
|
||||
func WithDisableNetworkFetch() *WithOption {
|
||||
return &WithOption{DisableNetworkFetch: &trueVar}
|
||||
}
|
||||
|
||||
func mergeOptions(opts ...*WithOption) *WithOption {
|
||||
// Grab options from the environs by default
|
||||
defaultChroot := "/"
|
||||
if val, exists := os.LookupEnv("PCIDB_CHROOT"); exists {
|
||||
defaultChroot = val
|
||||
}
|
||||
defaultCacheOnly := false
|
||||
if val, exists := os.LookupEnv("PCIDB_CACHE_ONLY"); exists {
|
||||
if parsed, err := strconv.ParseBool(val); err != nil {
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
"Failed parsing a bool from PCIDB_CACHE_ONLY "+
|
||||
"environ value of %s",
|
||||
val,
|
||||
)
|
||||
} else if parsed {
|
||||
defaultCacheOnly = parsed
|
||||
}
|
||||
}
|
||||
defaultDisableNetworkFetch := false
|
||||
if val, exists := os.LookupEnv("PCIDB_DISABLE_NETWORK_FETCH"); exists {
|
||||
if parsed, err := strconv.ParseBool(val); err != nil {
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
"Failed parsing a bool from PCIDB_DISABLE_NETWORK_FETCH "+
|
||||
"environ value of %s",
|
||||
val,
|
||||
)
|
||||
} else if parsed {
|
||||
defaultDisableNetworkFetch = parsed
|
||||
}
|
||||
}
|
||||
|
||||
merged := &WithOption{}
|
||||
for _, opt := range opts {
|
||||
if opt.Chroot != nil {
|
||||
merged.Chroot = opt.Chroot
|
||||
}
|
||||
if opt.CacheOnly != nil {
|
||||
merged.CacheOnly = opt.CacheOnly
|
||||
}
|
||||
if opt.DisableNetworkFetch != nil {
|
||||
merged.DisableNetworkFetch = opt.DisableNetworkFetch
|
||||
}
|
||||
}
|
||||
// Set the default value if missing from merged
|
||||
if merged.Chroot == nil {
|
||||
merged.Chroot = &defaultChroot
|
||||
}
|
||||
if merged.CacheOnly == nil {
|
||||
merged.CacheOnly = &defaultCacheOnly
|
||||
}
|
||||
if merged.DisableNetworkFetch == nil {
|
||||
merged.DisableNetworkFetch = &defaultDisableNetworkFetch
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// New returns a pointer to a PCIDB struct which contains information you can
|
||||
// use to query PCI vendor, product and class information. It accepts zero or
|
||||
// more pointers to WithOption structs. If you want to modify the behaviour of
|
||||
// pcidb, use one of the option modifiers when calling New. For example, to
|
||||
// change the root directory that pcidb uses when discovering pciids DB files,
|
||||
// call New(WithChroot("/my/root/override"))
|
||||
func New(opts ...*WithOption) (*PCIDB, error) {
|
||||
ctx := contextFromOptions(mergeOptions(opts...))
|
||||
db := &PCIDB{}
|
||||
if err := db.load(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// Use and distribution licensed under the Apache license version 2.
|
||||
//
|
||||
// See the COPYING file in the root project directory for full text.
|
||||
//
|
||||
|
||||
package pcidb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseDBFile(db *PCIDB, scanner *bufio.Scanner) error {
|
||||
inClassBlock := false
|
||||
db.Classes = make(map[string]*Class, 20)
|
||||
db.Vendors = make(map[string]*Vendor, 200)
|
||||
db.Products = make(map[string]*Product, 1000)
|
||||
subclasses := make([]*Subclass, 0)
|
||||
progIfaces := make([]*ProgrammingInterface, 0)
|
||||
var curClass *Class
|
||||
var curSubclass *Subclass
|
||||
var curProgIface *ProgrammingInterface
|
||||
vendorProducts := make([]*Product, 0)
|
||||
var curVendor *Vendor
|
||||
var curProduct *Product
|
||||
var curSubsystem *Product
|
||||
productSubsystems := make([]*Product, 0)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// skip comments and blank lines
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
lineBytes := []rune(line)
|
||||
|
||||
// Lines starting with an uppercase "C" indicate a PCI top-level class
|
||||
// dbrmation block. These lines look like this:
|
||||
//
|
||||
// C 02 Network controller
|
||||
if lineBytes[0] == 'C' {
|
||||
if curClass != nil {
|
||||
// finalize existing class because we found a new class block
|
||||
curClass.Subclasses = subclasses
|
||||
subclasses = make([]*Subclass, 0)
|
||||
}
|
||||
inClassBlock = true
|
||||
classID := string(lineBytes[2:4])
|
||||
className := string(lineBytes[6:])
|
||||
curClass = &Class{
|
||||
ID: classID,
|
||||
Name: className,
|
||||
Subclasses: subclasses,
|
||||
}
|
||||
db.Classes[curClass.ID] = curClass
|
||||
continue
|
||||
}
|
||||
|
||||
// Lines not beginning with an uppercase "C" or a TAB character
|
||||
// indicate a top-level vendor dbrmation block. These lines look like
|
||||
// this:
|
||||
//
|
||||
// 0a89 BREA Technologies Inc
|
||||
if lineBytes[0] != '\t' {
|
||||
if curVendor != nil {
|
||||
// finalize existing vendor because we found a new vendor block
|
||||
curVendor.Products = vendorProducts
|
||||
vendorProducts = make([]*Product, 0)
|
||||
}
|
||||
inClassBlock = false
|
||||
vendorID := string(lineBytes[0:4])
|
||||
vendorName := string(lineBytes[6:])
|
||||
curVendor = &Vendor{
|
||||
ID: vendorID,
|
||||
Name: vendorName,
|
||||
Products: vendorProducts,
|
||||
}
|
||||
db.Vendors[curVendor.ID] = curVendor
|
||||
continue
|
||||
}
|
||||
|
||||
// Lines beginning with only a single TAB character are *either* a
|
||||
// subclass OR are a device dbrmation block. If we're in a class
|
||||
// block (i.e. the last parsed block header was for a PCI class), then
|
||||
// we parse a subclass block. Otherwise, we parse a device dbrmation
|
||||
// block.
|
||||
//
|
||||
// A subclass dbrmation block looks like this:
|
||||
//
|
||||
// \t00 Non-VGA unclassified device
|
||||
//
|
||||
// A device dbrmation block looks like this:
|
||||
//
|
||||
// \t0002 PCI to MCA Bridge
|
||||
if len(lineBytes) > 1 && lineBytes[1] != '\t' {
|
||||
if inClassBlock {
|
||||
if curSubclass != nil {
|
||||
// finalize existing subclass because we found a new subclass block
|
||||
curSubclass.ProgrammingInterfaces = progIfaces
|
||||
progIfaces = make([]*ProgrammingInterface, 0)
|
||||
}
|
||||
subclassID := string(lineBytes[1:3])
|
||||
subclassName := string(lineBytes[5:])
|
||||
curSubclass = &Subclass{
|
||||
ID: subclassID,
|
||||
Name: subclassName,
|
||||
ProgrammingInterfaces: progIfaces,
|
||||
}
|
||||
subclasses = append(subclasses, curSubclass)
|
||||
} else {
|
||||
if curProduct != nil {
|
||||
// finalize existing product because we found a new product block
|
||||
curProduct.Subsystems = productSubsystems
|
||||
productSubsystems = make([]*Product, 0)
|
||||
}
|
||||
productID := string(lineBytes[1:5])
|
||||
productName := string(lineBytes[7:])
|
||||
productKey := curVendor.ID + productID
|
||||
curProduct = &Product{
|
||||
VendorID: curVendor.ID,
|
||||
ID: productID,
|
||||
Name: productName,
|
||||
}
|
||||
vendorProducts = append(vendorProducts, curProduct)
|
||||
db.Products[productKey] = curProduct
|
||||
}
|
||||
} else {
|
||||
// Lines beginning with two TAB characters are *either* a subsystem
|
||||
// (subdevice) OR are a programming interface for a PCI device
|
||||
// subclass. If we're in a class block (i.e. the last parsed block
|
||||
// header was for a PCI class), then we parse a programming
|
||||
// interface block, otherwise we parse a subsystem block.
|
||||
//
|
||||
// A programming interface block looks like this:
|
||||
//
|
||||
// \t\t00 UHCI
|
||||
//
|
||||
// A subsystem block looks like this:
|
||||
//
|
||||
// \t\t0e11 4091 Smart Array 6i
|
||||
if inClassBlock {
|
||||
progIfaceID := string(lineBytes[2:4])
|
||||
progIfaceName := string(lineBytes[6:])
|
||||
curProgIface = &ProgrammingInterface{
|
||||
ID: progIfaceID,
|
||||
Name: progIfaceName,
|
||||
}
|
||||
progIfaces = append(progIfaces, curProgIface)
|
||||
} else {
|
||||
vendorID := string(lineBytes[2:6])
|
||||
subsystemID := string(lineBytes[7:11])
|
||||
subsystemName := string(lineBytes[13:])
|
||||
curSubsystem = &Product{
|
||||
VendorID: vendorID,
|
||||
ID: subsystemID,
|
||||
Name: subsystemName,
|
||||
}
|
||||
productSubsystems = append(productSubsystems, curSubsystem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user