blob: bae396b2aeb04f1115bfa7ed1818ef5ca4294bf6 [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;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070025import org.onosproject.gnmi.api.GnmiClient;
26import org.onosproject.gnmi.api.GnmiController;
27import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
Yi Tseng59d5f3e2018-11-27 23:09:41 -080028import org.onosproject.net.Port;
29import org.onosproject.net.PortNumber;
30import org.onosproject.net.device.DefaultPortStatistics;
31import org.onosproject.net.device.PortStatistics;
32import org.onosproject.net.device.PortStatisticsDiscovery;
33
34import java.time.Duration;
35import java.time.temporal.ChronoUnit;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.List;
39import java.util.Map;
40import java.util.stream.Collectors;
41
42/**
43 * Behaviour to get port statistics from device via gNMI.
44 */
Carmelo Casconec2be50a2019-04-10 00:15:39 -070045public class OpenConfigGnmiPortStatisticsDiscovery
46 extends AbstractGrpcHandlerBehaviour<GnmiClient, GnmiController>
Yi Tseng59d5f3e2018-11-27 23:09:41 -080047 implements PortStatisticsDiscovery {
48
pierventre50696a72021-06-01 12:26:36 +020049 private static final String LAST_CHANGE = "last-change";
Yi Tseng59d5f3e2018-11-27 23:09:41 -080050
Carmelo Casconec2be50a2019-04-10 00:15:39 -070051 public OpenConfigGnmiPortStatisticsDiscovery() {
52 super(GnmiController.class);
53 }
54
Yi Tseng59d5f3e2018-11-27 23:09:41 -080055 @Override
56 public Collection<PortStatistics> discoverPortStatistics() {
Carmelo Casconec32976e2019-04-08 14:50:52 -070057 if (!setupBehaviour("discoverPortStatistics()")) {
Yi Tseng59d5f3e2018-11-27 23:09:41 -080058 return Collections.emptyList();
59 }
60
61 Map<String, PortNumber> ifacePortNumberMapping = Maps.newHashMap();
62 List<Port> ports = deviceService.getPorts(deviceId);
63 GetRequest.Builder getRequest = GetRequest.newBuilder();
64 getRequest.setEncoding(Gnmi.Encoding.PROTO);
65
66 // Use this path to get all counters from specific interface(port)
67 // /interfaces/interface[port-name]/state/counters/[counter name]
68 ports.forEach(port -> {
69 String portName = port.number().name();
70 Path path = interfaceCounterPath(portName);
71 getRequest.addPath(path);
72 ifacePortNumberMapping.put(portName, port.number());
73 });
74
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080075 GetResponse getResponse = Futures.getUnchecked(client.get(getRequest.build()));
Yi Tseng59d5f3e2018-11-27 23:09:41 -080076
77 Map<String, Long> inPkts = Maps.newHashMap();
78 Map<String, Long> outPkts = Maps.newHashMap();
79 Map<String, Long> inBytes = Maps.newHashMap();
80 Map<String, Long> outBytes = Maps.newHashMap();
81 Map<String, Long> inDropped = Maps.newHashMap();
82 Map<String, Long> outDropped = Maps.newHashMap();
83 Map<String, Long> inErrors = Maps.newHashMap();
84 Map<String, Long> outErrors = Maps.newHashMap();
85 Map<String, Duration> timestamps = Maps.newHashMap();
86
87 // Collect responses and sum {in,out,dropped} packets
88 getResponse.getNotificationList().forEach(notification -> {
89 notification.getUpdateList().forEach(update -> {
90 Path path = update.getPath();
91 String ifName = interfaceNameFromPath(path);
92 timestamps.putIfAbsent(ifName, Duration.ofNanos(notification.getTimestamp()));
93
94 // Last element is the counter name
95 String counterName = path.getElem(path.getElemCount() - 1).getName();
96 long counterValue = update.getVal().getUintVal();
97
98
99 switch (counterName) {
100 case "in-octets":
101 inBytes.put(ifName, counterValue);
102 break;
103 case "out-octets":
104 outBytes.put(ifName, counterValue);
105 break;
106 case "in-discards":
107 case "in-fcs-errors":
108 inDropped.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
109 break;
110 case "out-discards":
111 outDropped.put(ifName, counterValue);
112 break;
113 case "in-errors":
114 inErrors.put(ifName, counterValue);
115 break;
116 case "out-errors":
117 outErrors.put(ifName, counterValue);
118 break;
119 case "in-unicast-pkts":
120 case "in-broadcast-pkts":
121 case "in-multicast-pkts":
122 case "in-unknown-protos":
123 inPkts.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
124 break;
125 case "out-unicast-pkts":
126 case "out-broadcast-pkts":
127 case "out-multicast-pkts":
128 outPkts.compute(ifName, (k, v) -> v == null ? counterValue : v + counterValue);
129 break;
130 default:
131 log.warn("Unsupported counter name {}, ignored", counterName);
132 break;
133 }
134 });
135 });
136
137 // Build ONOS port stats map
138 return ifacePortNumberMapping.entrySet().stream()
139 .map(e -> {
140 String ifName = e.getKey();
141 PortNumber portNumber = e.getValue();
142 Duration portActive = getDurationActive(portNumber, timestamps.get(ifName));
143 return DefaultPortStatistics.builder()
144 .setDeviceId(deviceId)
145 .setPort(portNumber)
146 .setDurationSec(portActive.getSeconds())
147 .setDurationNano(portActive.getNano())
148 .setPacketsSent(outPkts.getOrDefault(ifName, 0L))
149 .setPacketsReceived(inPkts.getOrDefault(ifName, 0L))
150 .setPacketsTxDropped(outDropped.getOrDefault(ifName, 0L))
151 .setPacketsRxDropped(inDropped.getOrDefault(ifName, 0L))
152 .setBytesSent(outBytes.getOrDefault(ifName, 0L))
153 .setBytesReceived(inBytes.getOrDefault(ifName, 0L))
154 .setPacketsTxErrors(outErrors.getOrDefault(ifName, 0L))
155 .setPacketsRxErrors(inErrors.getOrDefault(ifName, 0L))
156 .build();
157 })
158 .collect(Collectors.toList());
159
160 }
161
162 private String interfaceNameFromPath(Path path) {
163 // /interfaces/interface[name=iface-name]
164 return path.getElem(1).getKeyOrDefault("name", null);
165 }
166
167 private Path interfaceCounterPath(String portName) {
168 // /interfaces/interface[name=port-name]/state/counters
169 return Path.newBuilder()
170 .addElem(Gnmi.PathElem.newBuilder().setName("interfaces").build())
171 .addElem(Gnmi.PathElem.newBuilder().setName("interface")
172 .putKey("name", portName).build())
173 .addElem(Gnmi.PathElem.newBuilder().setName("state").build())
174 .addElem(Gnmi.PathElem.newBuilder().setName("counters").build())
175 .build();
176 }
177
178 private Duration getDurationActive(PortNumber portNumber, Duration timestamp) {
179 Port port = deviceService.getPort(deviceId, portNumber);
180 if (port == null || !port.isEnabled()) {
181 //FIXME log
182 return Duration.ZERO;
183 }
pierventre50696a72021-06-01 12:26:36 +0200184
185 // Set duration 0 for devices that do not support reporting last-change
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800186 String lastChangedStr = port.annotations().value(LAST_CHANGE);
187 if (lastChangedStr == null) {
pierventre50696a72021-06-01 12:26:36 +0200188 return Duration.ZERO;
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800189 }
190
191 try {
192 long lastChanged = Long.parseLong(lastChangedStr);
pierventre50696a72021-06-01 12:26:36 +0200193 return lastChanged == 0 ? Duration.ZERO : timestamp.minus(lastChanged, ChronoUnit.NANOS);
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800194 } catch (NullPointerException | NumberFormatException ex) {
195 //FIXME log
196 return Duration.ZERO;
197 }
198 }
199}