diff --git a/render/id.go b/render/id.go index 471a38d0df..2a40be5335 100644 --- a/render/id.go +++ b/render/id.go @@ -1,7 +1,6 @@ package render import ( - "net" "strings" "github.com/weaveworks/scope/probe/endpoint" @@ -66,7 +65,9 @@ func externalNodeID(n report.Node, addr string, local report.Networks) (string, // If the dstNodeAddr is not in a network local to this report, we emit an // internet pseudoNode - if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) { + // Create a buffer on the stack of this function, so we don't need to allocate in ParseIP + var into [5]byte // one extra byte to save a memory allocation in critbitgo + if ip := report.ParseIP([]byte(addr), into[:4]); ip != nil && !local.Contains(ip) { // emit one internet node for incoming, one for outgoing if len(n.Adjacency) > 0 { return IncomingInternetID, true diff --git a/report/id.go b/report/id.go index b0a9aa3237..f0d3dec9bb 100644 --- a/report/id.go +++ b/report/id.go @@ -166,11 +166,11 @@ func makeSingleComponentID(tag string) func(string) string { // parseSingleComponentID makes a single-component node id decoder func parseSingleComponentID(tag string) func(string) (string, bool) { return func(id string) (string, bool) { - fields := strings.SplitN(id, ScopeDelim, 2) - if len(fields) != 2 || fields[1] != "<"+tag+">" { + field0, field1, ok := split2(id, ScopeDelim) + if !ok || field1 != "<"+tag+">" { return "", false } - return fields[0], true + return field0, true } } @@ -197,48 +197,54 @@ func ParseOverlayNodeID(id string) (overlayPrefix string, peerName string) { return WeaveOverlayPeerPrefix, id } +// Split a string s into to parts separated by sep. +func split2(s, sep string) (s1, s2 string, ok bool) { + // Not using strings.SplitN() to avoid a heap allocation + pos := strings.Index(s, sep) + if pos == -1 { + return "", "", false + } + return s[:pos], s[pos+1:], true +} + // ParseNodeID produces the host ID and remainder (typically an address) from // a node ID. Note that hostID may be blank. func ParseNodeID(nodeID string) (hostID string, remainder string, ok bool) { - fields := strings.SplitN(nodeID, ScopeDelim, 2) - if len(fields) != 2 { - return "", "", false - } - return fields[0], fields[1], true + return split2(nodeID, ScopeDelim) } // ParseEndpointNodeID produces the scope, address, and port and remainder. -// Note that hostID may be blank. +// Note that scope may be blank. func ParseEndpointNodeID(endpointNodeID string) (scope, address, port string, ok bool) { - fields := strings.SplitN(endpointNodeID, ScopeDelim, 3) - if len(fields) != 3 { + // Not using strings.SplitN() to avoid a heap allocation + first := strings.Index(endpointNodeID, ScopeDelim) + if first == -1 { return "", "", "", false } - - return fields[0], fields[1], fields[2], true + second := strings.Index(endpointNodeID[first+1:], ScopeDelim) + if second == -1 { + return "", "", "", false + } + return endpointNodeID[:first], endpointNodeID[first+1 : first+1+second], endpointNodeID[first+1+second+1:], true } // ParseAddressNodeID produces the host ID, address from an address node ID. func ParseAddressNodeID(addressNodeID string) (hostID, address string, ok bool) { - fields := strings.SplitN(addressNodeID, ScopeDelim, 2) - if len(fields) != 2 { - return "", "", false - } - return fields[0], fields[1], true + return split2(addressNodeID, ScopeDelim) } // ParseECSServiceNodeID produces the cluster, service name from an ECS Service node ID func ParseECSServiceNodeID(ecsServiceNodeID string) (cluster, serviceName string, ok bool) { - fields := strings.SplitN(ecsServiceNodeID, ScopeDelim, 2) - if len(fields) != 2 { + cluster, serviceName, ok = split2(ecsServiceNodeID, ScopeDelim) + if !ok { return "", "", false } // In previous versions, ECS Service node IDs were of form serviceName + "". // For backwards compatibility, we should still return a sensical serviceName for these cases. - if fields[1] == "" { - return "unknown", fields[0], true + if serviceName == "" { + return "unknown", cluster, true } - return fields[0], fields[1], true + return cluster, serviceName, true } // ExtractHostID extracts the host id from Node diff --git a/report/networks.go b/report/networks.go index ae0e4148af..d1b97ac738 100644 --- a/report/networks.go +++ b/report/networks.go @@ -140,3 +140,70 @@ func commonIPv4PrefixLen(a, b net.IP) int { } return cpl } + +// ParseIP parses s as an IP address into a byte slice if supplied, returning the result. +// (mostly copied from net.ParseIP, modified to save memory allocations) +func ParseIP(s []byte, into []byte) net.IP { + for i := 0; i < len(s); i++ { + switch s[i] { + case '.': + return parseIPv4(s, into) + case ':': + return net.ParseIP(string(s)) // leave IPv6 to the original code since we don't see many of those + } + } + return nil +} + +// Parse IPv4 address (d.d.d.d). +// (mostly copied from net.parseIPv4, modified to save memory allocations) +func parseIPv4(s []byte, into []byte) net.IP { + var p []byte + if len(into) >= net.IPv4len { // check if we can use the supplied slice + p = into[:net.IPv4len] + } else { + p = make([]byte, net.IPv4len) + } + for i := 0; i < net.IPv4len; i++ { + if len(s) == 0 { + // Missing octets. + return nil + } + if i > 0 { + if s[0] != '.' { + return nil + } + s = s[1:] + } + n, c, ok := dtoi(s) + if !ok || n > 0xFF { + return nil + } + s = s[c:] + p[i] = byte(n) + } + if len(s) != 0 { + return nil + } + return p +} + +// Bigger than we need, not too big to worry about overflow +const big = 0xFFFFFF + +// Decimal to integer. +// Returns number, characters consumed, success. +// (completely copied from net.dtoi, just because it wasn't exported) +func dtoi(s []byte) (n int, i int, ok bool) { + n = 0 + for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n >= big { + return big, i, false + } + } + if i == 0 { + return 0, 0, false + } + return n, i, true +}