Conversation
|
Thanks for the contribution. I'm traveling right now, but will try and get this reviewed soon. |
grobie
left a comment
There was a problem hiding this comment.
I'm generally not a fan of using reflect, it trades compile time checks for a few saved lines. Static code would be preferred.
I won't object merging a reflect based implementation, but please ensure there are no linter errors (e.g. ensure all types and functions are properly documented).
sysfs/net_class.go
Outdated
| "strings" | ||
| ) | ||
|
|
||
| type interfaceNetClass struct { |
There was a problem hiding this comment.
The struct should be exported with a descriptive name, so that it'll be be included in the documentation https://godoc.org/github.com/prometheus/procfs/.
There was a problem hiding this comment.
Ok, does NetClassInterface seem like a better name to you? I'm not sure whether using word interface is really a good idea - can be misleading language-wise, on the other hand something like device is misleading as well
There was a problem hiding this comment.
I believe following the terminology chosen by the kernel developers makes the most sense. Reading the kernel documentation, it should be either iface or interface. I'm generally not a fan of abbreviations and would slightly lean towards interface. In the doc strings we could use interface (iface) to make the context clear.
sysfs/net_class.go
Outdated
| ) | ||
|
|
||
| type interfaceNetClass struct { | ||
| Name string `fileName:"name"` // Interface name |
There was a problem hiding this comment.
I'm worried including this field can lead to confusion, as it's not actually an attribute in /sys/class/net/*/. I see the benefit of having it here though. I'd remove the filename attribute here to not further confuse a reader.
There was a problem hiding this comment.
That is a correct note and mistake on my side, I'll remove the tag, thanks.
sysfs/net_class.go
Outdated
| CarrierChanges uint64 `fileName:"carrier_changes"` // /sys/class/net/<iface>/carrier_changes | ||
| CarrierUpCount uint64 `fileName:"carrier_up_count"` // /sys/class/net/<iface>/carrier_up_count | ||
| CarrierDownCount uint64 `fileName:"carrier_down_count"` // /sys/class/net/<iface>/carrier_down_count | ||
| DevId uint64 `fileName:"dev_id"` // /sys/class/net/<iface>/dev_id |
There was a problem hiding this comment.
There was a problem hiding this comment.
Did not know that. Thanks!
sysfs/net_class.go
Outdated
| IfIndex uint64 `fileName:"ifindex"` // /sys/class/net/<iface>/ifindex | ||
| IfLink uint64 `fileName:"iflink"` // /sys/class/net/<iface>/iflink | ||
| LinkMode uint64 `fileName:"link_mode"` // /sys/class/net/<iface>/link_mode | ||
| Mtu uint64 `fileName:"mtu"` // /sys/class/net/<iface>/link_mode |
sysfs/net_class.go
Outdated
| NameAssignType uint64 `fileName:"name_assign_type"` // /sys/class/net/<iface>/name_assign_type | ||
| NetDevGroup uint64 `fileName:"netdev_group"` // /sys/class/net/<iface>/netdev_group | ||
| OperState string `fileName:"operstate"` // /sys/class/net/<iface>/operstate | ||
| PhysPortId string `fileName:"phys_port_id"` // /sys/class/net/<iface>/phys_port_id |
sysfs/net_class.go
Outdated
| Type uint64 `fileName:"type"` // /sys/class/net/<iface>/type | ||
| } | ||
|
|
||
| type NetClass map[string]interfaceNetClass |
There was a problem hiding this comment.
Please add a godoc comment for that type and explain the map key.
sysfs/net_class.go
Outdated
|
|
||
| type NetClass map[string]interfaceNetClass | ||
|
|
||
| // NewNetDev returns kernel/system statistics read from /proc/net/dev. |
There was a problem hiding this comment.
godoc comment is out of sync. Please check your code with golint sys/... and go vet ./....
There was a problem hiding this comment.
I fixed the comment, but neither of those commands returned anything (neither before nor after the fix) not sure if that's correct or I'm doing sth wrong?
There was a problem hiding this comment.
There are indeed no go vet errors in the packages fortunately. A couple of linter errors though in other files (nfs and bcache). Before your changes, golint also complained about the missing or wrong godoc strings. You should see some output with golint ./... executed in the procfs repository root.
sysfs/net_class_test.go
Outdated
| t.Errorf("want %d parsed class/net, have %d", want, have) | ||
| } | ||
|
|
||
| fields := []string{"AddrAssignType", "AddrLen", "Address", "Broadcast", "Carrier", "CarrierChanges", "CarrierUpCount", "CarrierDownCount", "DevId", "Dormant", "Duplex", "Flags", "IfAlias", "IfIndex", "IfLink", "LinkMode", "Mtu", "NameAssignType", "NetDevGroup", "OperState", "PhysPortId", "PhysPortName", "PhysSwitchId", "Speed", "TxQueueLen", "Type"} |
There was a problem hiding this comment.
Is all of this code necessary? Wouldn't it be enough to use reflect.DeepEqual(netClass, nc)?
There was a problem hiding this comment.
absolutely, did not know that. thanks!
sysfs/net_class.go
Outdated
| } | ||
|
|
||
| func (nc NetClass) parseInterfaceNetClass(devicePath string) (*interfaceNetClass, error) { | ||
| fields := []string{"AddrAssignType", "AddrLen", "Address", "Broadcast", "Carrier", "CarrierChanges", "CarrierUpCount", "CarrierDownCount", "DevId", "Dormant", "Duplex", "Flags", "IfAlias", "IfIndex", "IfLink", "LinkMode", "Mtu", "NameAssignType", "NetDevGroup", "OperState", "PhysPortId", "PhysPortName", "PhysSwitchId", "Speed", "TxQueueLen", "Type"} |
There was a problem hiding this comment.
Why do you list all the field names again, given that you already use the reflect package? With reflect.ValueOf(&interfaceClass).NumField() you can get the number of fields and with reflect.ValueOf(&interfaceClass).Field(index) you can access a field by index.
There was a problem hiding this comment.
much better solution, thanks!
sysfs/net_class.go
Outdated
| } | ||
| fieldValue := interfaceElem.FieldByName(fieldName) | ||
|
|
||
| fileContents, err := ioutil.ReadFile(devicePath + "/" + fieldType.Tag.Get("fileName")) |
There was a problem hiding this comment.
There should be a check whether fileName tag is set before trying to open such file.
There was a problem hiding this comment.
definitely. Added, thanks for that
sysfs/net_class_test.go
Outdated
| } | ||
|
|
||
| netClass := NetClass{ | ||
| "eth0": {Name: "eth0", AddrAssignType: 3, AddrLen: 6, Address: "01:01:01:01:01:01", Broadcast: "ff:ff:ff:ff:ff:ff", Carrier: 1, CarrierChanges: 2, CarrierUpCount: 1, CarrierDownCount: 1, DevID: 32, Dormant: 1, Duplex: "full", Flags: 4867, IfAlias: "", IfIndex: 2, IfLink: 2, LinkMode: 1, MTU: 1500, NameAssignType: 2, NetDevGroup: 0, OperState: "up", PhysPortID: "", PhysPortName: "", PhysSwitchID: "", Speed: 1000, TxQueueLen: 1000, Type: 1}, |
There was a problem hiding this comment.
It'd help readability to have a line break after each attribute/value pair.
sysfs/net_class.go
Outdated
| return newNetClass(fs.Path("class/net")) | ||
| } | ||
|
|
||
| // NewNetClass returns info for all net interfaces read from /sys/class/net/<interface>. |
There was a problem hiding this comment.
This should start with a lowercase n to document the private function. I wonder why not just merging this fuction with the public NewNetClass function, given it's the only caller.
sysfs/net_class.go
Outdated
| "strings" | ||
| ) | ||
|
|
||
| // NetClassInterface contains info from files in /sys/class/net/<interface> |
There was a problem hiding this comment.
The kernel documentation uses <iface>, what about using the same?
There was a problem hiding this comment.
makes sense. Omits the possible confusion, thanks!
sysfs/net_class.go
Outdated
| ) | ||
|
|
||
| // NetClassInterface contains info from files in /sys/class/net/<interface> | ||
| // for single interface |
There was a problem hiding this comment.
What about adding for a single interface (iface). in parenthesis, to emphasize that it's not about a language/golang interface. Please end all comment sentences with full stops (following the golang style guide).
sysfs/net_class.go
Outdated
| } | ||
| value := strings.TrimSpace(string(fileContents)) | ||
|
|
||
| if fieldValue.Kind() == reflect.Uint64 { |
There was a problem hiding this comment.
I'd suggest to use a switch statement here and error handling in the default case, to ensure all value types are handled.
switch fieldValue.Kind() {
case reflect.Uint64:
// ...
default:
return nil, fmt.Errorf("unhandled type %q", fieldValue.Kind())
}
sysfs/net_class.go
Outdated
| } | ||
|
|
||
| // NetClass is collection of info for every interface in /sys/class/net. The map keys | ||
| // are interface names. |
There was a problem hiding this comment.
Probably here again to make it clearer interface (iface).
|
@grobie Do you have any suggestions to replace |
grobie
left a comment
There was a problem hiding this comment.
Thanks a lot for your contribution, looks great!
My only remaining question is around error handling. We currently ignore all errors and silently omit the data or even the whole interface. As procfs/sysfs are low level packages, I'd expect errors returned to be in that case, so that the caller can decide how to handle such situations.
One common error case I can imagine are non-existent files in older kernel versions (for attributes which were only added later). For that case you could use os.IsNotExist(err) and continue in that case without returning an error. For other file read errors or parser errors, I'd intuitively return an error. What do you think? Have you used the continue statements on purpose?
|
@SuperQ I often find static code easier to reason about then trying to abstract it away, my editor helps me with managing such problems. For a middleground between reflect usage and manually typed code I would look into |
|
@grobie Thanks for your thorough code review, I'm really glad for that! As for the error handling - yes I did use the When testing this on one of our debian machines, I got to a problem parsing the The other one is rather being cautious on my side, as I can imagine an interface dir not being accessible for some reason. In such case I don't think it is correct to return an error and ignoring all other ok ifaces, but that's really my point of view... |
|
@klatys We had a similar IO problem with |
|
In my experience strict error handling makes it easier to debug issues. I've witnessed many lengthy debug sessions due to silenced error swallowing. I'd suggest to handle the "file not exist" and "operation not supported" cases gracefully (and probably the EAGAIN error as well), but bubble up errors in all other cases. |
|
Ok, np. I implemented that and changed the error handling. besides the three file errors I had to add one other - Hopefully there won't be any other errors for these "files". Also I ran this on several machines and discovered that some values (e.g. speed) can be negative (e.g. -1) thus the type is now Int64. |
* add net class parsing * change tag, rename var * fix coding style * fix data types for name_assign_type and phys_switch_id * code review fixes * code review fixes - clearer naming * better error handling
Related to prometheus/node_exporter#300