Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sys class net #82

Merged
merged 7 commits into from
Mar 7, 2018
Merged

Sys class net #82

merged 7 commits into from
Mar 7, 2018

Conversation

klatys
Copy link
Contributor

@klatys klatys commented Mar 3, 2018

@SuperQ
Copy link
Member

SuperQ commented Mar 3, 2018

Thanks for the contribution. I'm traveling right now, but will try and get this reviewed soon.

@SuperQ SuperQ requested review from mdlayher and SuperQ March 3, 2018 21:12
Copy link
Member

@grobie grobie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

"strings"
)

type interfaceNetClass struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

)

type interfaceNetClass struct {
Name string `fileName:"name"` // Interface name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a correct note and mistake on my side, I'll remove the tag, thanks.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not know that. Thanks!

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MTU

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PhysPortID

Type uint64 `fileName:"type"` // /sys/class/net/<iface>/type
}

type NetClass map[string]interfaceNetClass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a godoc comment for that type and explain the map key.


type NetClass map[string]interfaceNetClass

// NewNetDev returns kernel/system statistics read from /proc/net/dev.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc comment is out of sync. Please check your code with golint sys/... and go vet ./....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is all of this code necessary? Wouldn't it be enough to use reflect.DeepEqual(netClass, nc)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absolutely, did not know that. thanks!

}

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"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much better solution, thanks!

}
fieldValue := interfaceElem.FieldByName(fieldName)

fileContents, err := ioutil.ReadFile(devicePath + "/" + fieldType.Tag.Get("fileName"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a check whether fileName tag is set before trying to open such file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely. Added, thanks for that

Copy link
Member

@grobie grobie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

}

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},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd help readability to have a line break after each attribute/value pair.

return newNetClass(fs.Path("class/net"))
}

// NewNetClass returns info for all net interfaces read from /sys/class/net/<interface>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absolutely, merging.

"strings"
)

// NetClassInterface contains info from files in /sys/class/net/<interface>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kernel documentation uses <iface>, what about using the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. Omits the possible confusion, thanks!

)

// NetClassInterface contains info from files in /sys/class/net/<interface>
// for single interface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

}
value := strings.TrimSpace(string(fileContents))

if fieldValue.Kind() == reflect.Uint64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())
}

}

// NetClass is collection of info for every interface in /sys/class/net. The map keys
// are interface names.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably here again to make it clearer interface (iface).

@SuperQ
Copy link
Member

SuperQ commented Mar 6, 2018

@grobie Do you have any suggestions to replace reflect to avoid the huge amount of copy-paste necessary for things like this vs with reflect?

Copy link
Member

@SuperQ SuperQ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@grobie grobie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@grobie
Copy link
Member

grobie commented Mar 6, 2018

@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 go generate. I only have used it once in my life so far, but it should be possible to auto-generate your linked functions.

@klatys
Copy link
Contributor Author

klatys commented Mar 6, 2018

@grobie Thanks for your thorough code review, I'm really glad for that!

As for the error handling - yes I did use the continue on purpose, for two specific reasons:

When testing this on one of our debian machines, I got to a problem parsing the phys_* files which cannot be read (e.g. $ cat /sys/class/net/eth0/phys_port_id produces Operation not supported error, go returns error read ...: operation not supported). From what our linux guys are telling me this is probably caused by the driver of the device and might not be something that unusual. The other thing is, as you mentioned, nonexistent file due to older kernel (which I could find on any of our machines) and in my opinion should not produce error as well.

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...

@SuperQ
Copy link
Member

SuperQ commented Mar 6, 2018

@klatys We had a similar IO problem with hwmon. Some kernels would return EAGAIN. We wrote a special reader function for this use case. We may want to do the same here.

@grobie
Copy link
Member

grobie commented Mar 6, 2018

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.

@klatys
Copy link
Contributor Author

klatys commented Mar 6, 2018

Ok, np. I implemented that and changed the error handling. besides the three file errors I had to add one other - invalid argument:

$cat /sys/class/net/lo/name_assign_type 
cat: /sys/class/net/lo/name_assign_type: Invalid argument

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.

@grobie grobie merged commit 1c7ff3d into prometheus:master Mar 7, 2018
@klatys klatys deleted the sys-class-net branch April 17, 2018 14:51
@klatys klatys restored the sys-class-net branch April 17, 2018 14:51
@klatys klatys deleted the sys-class-net branch April 17, 2018 14:52
remijouannet pushed a commit to remijouannet/procfs that referenced this pull request Oct 20, 2022
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants