blob: 6e361ff562810d134024b5c642b7b91e1fd261ae [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;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080020import com.google.common.util.concurrent.Futures;
Yi Tseng59d5f3e2018-11-27 23:09:41 -080021import gnmi.Gnmi;
22import gnmi.Gnmi.GetRequest;
23import gnmi.Gnmi.GetResponse;
24import gnmi.Gnmi.Path;
25import org.apache.commons.lang3.tuple.Pair;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070026import org.onosproject.gnmi.api.GnmiClient;
27import org.onosproject.gnmi.api.GnmiController;
28import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
Yi Tseng59d5f3e2018-11-27 23:09:41 -080029import org.onosproject.net.DeviceId;
30import org.onosproject.net.Port;
31import org.onosproject.net.PortNumber;
32import org.onosproject.net.device.DefaultPortStatistics;
33import org.onosproject.net.device.PortStatistics;
34import org.onosproject.net.device.PortStatisticsDiscovery;
35
36import java.time.Duration;
37import java.time.temporal.ChronoUnit;
38import java.util.Collection;
39import java.util.Collections;
40import java.util.List;
41import java.util.Map;
42import java.util.stream.Collectors;
43
44/**
45 * Behaviour to get port statistics from device via gNMI.
46 */
Carmelo Casconec2be50a2019-04-10 00:15:39 -070047public class OpenConfigGnmiPortStatisticsDiscovery
48 extends AbstractGrpcHandlerBehaviour<GnmiClient, GnmiController>
Yi Tseng59d5f3e2018-11-27 23:09:41 -080049 implements PortStatisticsDiscovery {
50
51 private static final Map<Pair<DeviceId, PortNumber>, Long> PORT_START_TIMES =
52 Maps.newConcurrentMap();
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080053 private static final String LAST_CHANGE = "last-changed";
Yi Tseng59d5f3e2018-11-27 23:09:41 -080054
Carmelo Casconec2be50a2019-04-10 00:15:39 -070055 public OpenConfigGnmiPortStatisticsDiscovery() {
56 super(GnmiController.class);
57 }
58
Yi Tseng59d5f3e2018-11-27 23:09:41 -080059 @Override
60 public Collection<PortStatistics> discoverPortStatistics() {
Carmelo Casconec32976e2019-04-08 14:50:52 -070061 if (!setupBehaviour("discoverPortStatistics()")) {
Yi Tseng59d5f3e2018-11-27 23:09:41 -080062 return Collections.emptyList();
63 }
64
65 Map<String, PortNumber> ifacePortNumberMapping = Maps.newHashMap();
66 List<Port> ports = deviceService.getPorts(deviceId);
67 GetRequest.Builder getRequest = GetRequest.newBuilder();
68 getRequest.setEncoding(Gnmi.Encoding.PROTO);
69
70 // Use this path to get all counters from specific interface(port)
71 // /interfaces/interface[port-name]/state/counters/[counter name]
72 ports.forEach(port -> {
73 String portName = port.number().name();
74 Path path = interfaceCounterPath(portName);
75 getRequest.addPath(path);
76 ifacePortNumberMapping.put(portName, port.number());
77 });
78
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080079 GetResponse getResponse = Futures.getUnchecked(client.get(getRequest.build()));
Yi Tseng59d5f3e2018-11-27 23:09:41 -080080
81 Map<String, Long> inPkts = Maps.newHashMap();
82 Map<String, Long> outPkts = Maps.newHashMap();
83 Map<String, Long> inBytes = Maps.newHashMap();
84 Map<String, Long> outBytes = Maps.newHashMap();
85 Map<String, Long> inDropped = Maps.newHashMap();
86 Map<String, Long> outDropped = Maps.newHashMap();
87 Map<String, Long> inErrors = Maps.newHashMap();
88 Map<String, Long> outErrors = Maps.newHashMap();
89 Map<String, Duration> timestamps = Maps.newHashMap();
90
91 // Collect responses and sum {in,out,dropped} packets
92 getResponse.getNotificationList().forEach(notification -> {
93 notification.getUpdateList().forEach(update -> {
94 Path path = update.getPath();
95 String ifName = interfaceNameFromPath(path);
96 timestamps.putIfAbsent(ifName, Duration.ofNanos(notification.getTimestamp()));
97
98 // Last element is the counter name
99 String counterName = path.getElem(path.getElemCount() - 1).getName();
100 long counterValue = update.getVal().getUintVal();
101
102
103 switch (counterName) {
104 case "in-octets":
105 inBytes.put(ifName, counterValue);
106 break;
107 case "out-octets":
108 outBytes.put(ifName, counterValue);
109 break;
110 case "in-discards":
111 case "in-fcs-errors":
112 inDropped.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
113 break;
114 case "out-discards":
115 outDropped.put(ifName, counterValue);
116 break;
117 case "in-errors":
118 inErrors.put(ifName, counterValue);
119 break;
120 case "out-errors":
121 outErrors.put(ifName, counterValue);
122 break;
123 case "in-unicast-pkts":
124 case "in-broadcast-pkts":
125 case "in-multicast-pkts":
126 case "in-unknown-protos":
127 inPkts.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
128 break;
129 case "out-unicast-pkts":
130 case "out-broadcast-pkts":
131 case "out-multicast-pkts":
132 outPkts.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
133 break;
134 default:
135 log.warn("Unsupported counter name {}, ignored", counterName);
136 break;
137 }
138 });
139 });
140
141 // Build ONOS port stats map
142 return ifacePortNumberMapping.entrySet().stream()
143 .map(e -> {
144 String ifName = e.getKey();
145 PortNumber portNumber = e.getValue();
146 Duration portActive = getDurationActive(portNumber, timestamps.get(ifName));
147 return DefaultPortStatistics.builder()
148 .setDeviceId(deviceId)
149 .setPort(portNumber)
150 .setDurationSec(portActive.getSeconds())
151 .setDurationNano(portActive.getNano())
152 .setPacketsSent(outPkts.getOrDefault(ifName, 0L))
153 .setPacketsReceived(inPkts.getOrDefault(ifName, 0L))
154 .setPacketsTxDropped(outDropped.getOrDefault(ifName, 0L))
155 .setPacketsRxDropped(inDropped.getOrDefault(ifName, 0L))
156 .setBytesSent(outBytes.getOrDefault(ifName, 0L))
157 .setBytesReceived(inBytes.getOrDefault(ifName, 0L))
158 .setPacketsTxErrors(outErrors.getOrDefault(ifName, 0L))
159 .setPacketsRxErrors(inErrors.getOrDefault(ifName, 0L))
160 .build();
161 })
162 .collect(Collectors.toList());
163
164 }
165
166 private String interfaceNameFromPath(Path path) {
167 // /interfaces/interface[name=iface-name]
168 return path.getElem(1).getKeyOrDefault("name", null);
169 }
170
171 private Path interfaceCounterPath(String portName) {
172 // /interfaces/interface[name=port-name]/state/counters
173 return Path.newBuilder()
174 .addElem(Gnmi.PathElem.newBuilder().setName("interfaces").build())
175 .addElem(Gnmi.PathElem.newBuilder().setName("interface")
176 .putKey("name", portName).build())
177 .addElem(Gnmi.PathElem.newBuilder().setName("state").build())
178 .addElem(Gnmi.PathElem.newBuilder().setName("counters").build())
179 .build();
180 }
181
182 private Duration getDurationActive(PortNumber portNumber, Duration timestamp) {
183 Port port = deviceService.getPort(deviceId, portNumber);
184 if (port == null || !port.isEnabled()) {
185 //FIXME log
186 return Duration.ZERO;
187 }
188 String lastChangedStr = port.annotations().value(LAST_CHANGE);
189 if (lastChangedStr == null) {
190 //FIXME log
191 // Falling back to the hack...
192 // FIXME: This is a workaround since we cannot determine the port
193 // duration from gNMI now
194 final long now = System.currentTimeMillis() / 1000;
195 final Long startTime = PORT_START_TIMES.putIfAbsent(
196 Pair.of(deviceId, portNumber), now);
197 return Duration.ofSeconds(startTime == null ? now : now - startTime);
198 }
199
200 try {
201 long lastChanged = Long.parseLong(lastChangedStr);
202 return timestamp.minus(lastChanged, ChronoUnit.NANOS);
203 } catch (NullPointerException | NumberFormatException ex) {
204 //FIXME log
205 return Duration.ZERO;
206 }
207 }
208}