blob: a9481a420c6df24b1593876bb06040e2bdbc0954 [file] [log] [blame]
Yi Tseng59d5f3e2018-11-27 23:09:41 -08001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.drivers.gnmi;
18
19import com.google.common.collect.Maps;
20import gnmi.Gnmi;
21import gnmi.Gnmi.GetRequest;
22import gnmi.Gnmi.GetResponse;
23import gnmi.Gnmi.Path;
24import org.apache.commons.lang3.tuple.Pair;
25import org.onosproject.net.DeviceId;
26import org.onosproject.net.Port;
27import org.onosproject.net.PortNumber;
28import org.onosproject.net.device.DefaultPortStatistics;
29import org.onosproject.net.device.PortStatistics;
30import org.onosproject.net.device.PortStatisticsDiscovery;
31
32import java.time.Duration;
33import java.time.temporal.ChronoUnit;
34import java.util.Collection;
35import java.util.Collections;
36import java.util.List;
37import java.util.Map;
38import java.util.stream.Collectors;
39
40/**
41 * Behaviour to get port statistics from device via gNMI.
42 */
43public class OpenConfigGnmiPortStatisticsDiscovery extends AbstractGnmiHandlerBehaviour
44 implements PortStatisticsDiscovery {
45
46 private static final Map<Pair<DeviceId, PortNumber>, Long> PORT_START_TIMES =
47 Maps.newConcurrentMap();
48 private static final String LAST_CHANGE = "last-change";
49
50 @Override
51 public Collection<PortStatistics> discoverPortStatistics() {
52 if (!setupBehaviour()) {
53 return Collections.emptyList();
54 }
55
56 Map<String, PortNumber> ifacePortNumberMapping = Maps.newHashMap();
57 List<Port> ports = deviceService.getPorts(deviceId);
58 GetRequest.Builder getRequest = GetRequest.newBuilder();
59 getRequest.setEncoding(Gnmi.Encoding.PROTO);
60
61 // Use this path to get all counters from specific interface(port)
62 // /interfaces/interface[port-name]/state/counters/[counter name]
63 ports.forEach(port -> {
64 String portName = port.number().name();
65 Path path = interfaceCounterPath(portName);
66 getRequest.addPath(path);
67 ifacePortNumberMapping.put(portName, port.number());
68 });
69
70 GetResponse getResponse =
71 getFutureWithDeadline(client.get(getRequest.build()),
72 "getting port counters",
73 GetResponse.getDefaultInstance());
74
75 Map<String, Long> inPkts = Maps.newHashMap();
76 Map<String, Long> outPkts = Maps.newHashMap();
77 Map<String, Long> inBytes = Maps.newHashMap();
78 Map<String, Long> outBytes = Maps.newHashMap();
79 Map<String, Long> inDropped = Maps.newHashMap();
80 Map<String, Long> outDropped = Maps.newHashMap();
81 Map<String, Long> inErrors = Maps.newHashMap();
82 Map<String, Long> outErrors = Maps.newHashMap();
83 Map<String, Duration> timestamps = Maps.newHashMap();
84
85 // Collect responses and sum {in,out,dropped} packets
86 getResponse.getNotificationList().forEach(notification -> {
87 notification.getUpdateList().forEach(update -> {
88 Path path = update.getPath();
89 String ifName = interfaceNameFromPath(path);
90 timestamps.putIfAbsent(ifName, Duration.ofNanos(notification.getTimestamp()));
91
92 // Last element is the counter name
93 String counterName = path.getElem(path.getElemCount() - 1).getName();
94 long counterValue = update.getVal().getUintVal();
95
96
97 switch (counterName) {
98 case "in-octets":
99 inBytes.put(ifName, counterValue);
100 break;
101 case "out-octets":
102 outBytes.put(ifName, counterValue);
103 break;
104 case "in-discards":
105 case "in-fcs-errors":
106 inDropped.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
107 break;
108 case "out-discards":
109 outDropped.put(ifName, counterValue);
110 break;
111 case "in-errors":
112 inErrors.put(ifName, counterValue);
113 break;
114 case "out-errors":
115 outErrors.put(ifName, counterValue);
116 break;
117 case "in-unicast-pkts":
118 case "in-broadcast-pkts":
119 case "in-multicast-pkts":
120 case "in-unknown-protos":
121 inPkts.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
122 break;
123 case "out-unicast-pkts":
124 case "out-broadcast-pkts":
125 case "out-multicast-pkts":
126 outPkts.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
127 break;
128 default:
129 log.warn("Unsupported counter name {}, ignored", counterName);
130 break;
131 }
132 });
133 });
134
135 // Build ONOS port stats map
136 return ifacePortNumberMapping.entrySet().stream()
137 .map(e -> {
138 String ifName = e.getKey();
139 PortNumber portNumber = e.getValue();
140 Duration portActive = getDurationActive(portNumber, timestamps.get(ifName));
141 return DefaultPortStatistics.builder()
142 .setDeviceId(deviceId)
143 .setPort(portNumber)
144 .setDurationSec(portActive.getSeconds())
145 .setDurationNano(portActive.getNano())
146 .setPacketsSent(outPkts.getOrDefault(ifName, 0L))
147 .setPacketsReceived(inPkts.getOrDefault(ifName, 0L))
148 .setPacketsTxDropped(outDropped.getOrDefault(ifName, 0L))
149 .setPacketsRxDropped(inDropped.getOrDefault(ifName, 0L))
150 .setBytesSent(outBytes.getOrDefault(ifName, 0L))
151 .setBytesReceived(inBytes.getOrDefault(ifName, 0L))
152 .setPacketsTxErrors(outErrors.getOrDefault(ifName, 0L))
153 .setPacketsRxErrors(inErrors.getOrDefault(ifName, 0L))
154 .build();
155 })
156 .collect(Collectors.toList());
157
158 }
159
160 private String interfaceNameFromPath(Path path) {
161 // /interfaces/interface[name=iface-name]
162 return path.getElem(1).getKeyOrDefault("name", null);
163 }
164
165 private Path interfaceCounterPath(String portName) {
166 // /interfaces/interface[name=port-name]/state/counters
167 return Path.newBuilder()
168 .addElem(Gnmi.PathElem.newBuilder().setName("interfaces").build())
169 .addElem(Gnmi.PathElem.newBuilder().setName("interface")
170 .putKey("name", portName).build())
171 .addElem(Gnmi.PathElem.newBuilder().setName("state").build())
172 .addElem(Gnmi.PathElem.newBuilder().setName("counters").build())
173 .build();
174 }
175
176 private Duration getDurationActive(PortNumber portNumber, Duration timestamp) {
177 Port port = deviceService.getPort(deviceId, portNumber);
178 if (port == null || !port.isEnabled()) {
179 //FIXME log
180 return Duration.ZERO;
181 }
182 String lastChangedStr = port.annotations().value(LAST_CHANGE);
183 if (lastChangedStr == null) {
184 //FIXME log
185 // Falling back to the hack...
186 // FIXME: This is a workaround since we cannot determine the port
187 // duration from gNMI now
188 final long now = System.currentTimeMillis() / 1000;
189 final Long startTime = PORT_START_TIMES.putIfAbsent(
190 Pair.of(deviceId, portNumber), now);
191 return Duration.ofSeconds(startTime == null ? now : now - startTime);
192 }
193
194 try {
195 long lastChanged = Long.parseLong(lastChangedStr);
196 return timestamp.minus(lastChanged, ChronoUnit.NANOS);
197 } catch (NullPointerException | NumberFormatException ex) {
198 //FIXME log
199 return Duration.ZERO;
200 }
201 }
202}