/* | |
* Copyright 2015 Open Networking Laboratory | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.onosproject.cli.net; | |
import org.apache.karaf.shell.commands.Argument; | |
import org.apache.karaf.shell.commands.Command; | |
import org.apache.karaf.shell.commands.Option; | |
import org.onosproject.cli.AbstractShellCommand; | |
import org.onosproject.net.ConnectPoint; | |
import org.onosproject.net.Device; | |
import org.onosproject.net.DeviceId; | |
import org.onosproject.net.Port; | |
import org.onosproject.net.PortNumber; | |
import org.onosproject.net.device.DeviceService; | |
import org.onosproject.net.flow.TypedStoredFlowEntry; | |
import org.onosproject.net.flow.instructions.Instruction; | |
import org.onosproject.net.statistic.FlowStatisticService; | |
import org.onosproject.net.statistic.SummaryFlowEntryWithLoad; | |
import org.onosproject.net.statistic.TypedFlowEntryWithLoad; | |
import java.util.List; | |
import java.util.Map; | |
import static org.onosproject.net.DeviceId.deviceId; | |
import static org.onosproject.net.PortNumber.portNumber; | |
/** | |
* Fetches flow statistics with a flow type and instruction type. | |
*/ | |
@Command(scope = "onos", name = "get-flow-stats", | |
description = "Fetches flow stats for a connection point with given flow type and instruction type") | |
public class GetFlowStatistics extends AbstractShellCommand { | |
@Argument(index = 0, name = "devicePort", | |
description = "Device[/Port] connectPoint Description", | |
required = true, multiValued = false) | |
String devicePort = null; | |
@Option(name = "-s", aliases = "--summary", | |
description = "Show flow stats summary", | |
required = false, multiValued = false) | |
boolean showSummary = true; // default summary | |
@Option(name = "-a", aliases = "--all", | |
description = "Show flow stats all", | |
required = false, multiValued = false) | |
boolean showAll = false; | |
@Option(name = "-t", aliases = "--topn", | |
description = "Show flow stats topn", | |
required = false, multiValued = false) | |
String showTopn = null; | |
@Option(name = "-f", aliases = "--flowType", | |
description = "Flow live type, It includes IMMEDIATE, SHORT, MID, LONG, UNKNOWN" | |
+ ", and is valid with -a or -t option only", | |
required = false, multiValued = false) | |
String flowLiveType = null; | |
@Option(name = "-i", aliases = "--instructionType", | |
description = "Flow instruction type, It includes DROP, OUTPUT, GROUP, L0MODIFICATION, L2MODIFICATION," | |
+ " TABLE, L3MODIFICATION, METADATA" | |
+ ", and is valid with -a or -t option only", | |
required = false, multiValued = false) | |
String instructionType = null; | |
@Override | |
protected void execute() { | |
DeviceService deviceService = get(DeviceService.class); | |
FlowStatisticService flowStatsService = get(FlowStatisticService.class); | |
String deviceURI = getDeviceId(devicePort); | |
String portURI = getPortNumber(devicePort); | |
DeviceId ingressDeviceId = deviceId(deviceURI); | |
PortNumber ingressPortNumber; | |
if (portURI.length() == 0) { | |
ingressPortNumber = null; | |
} else { | |
ingressPortNumber = portNumber(portURI); | |
} | |
Device device = deviceService.getDevice(ingressDeviceId); | |
if (device == null) { | |
error("No such device %s", ingressDeviceId.uri()); | |
return; | |
} | |
if (ingressPortNumber != null) { | |
Port port = deviceService.getPort(ingressDeviceId, ingressPortNumber); | |
if (port == null) { | |
error("No such port %s on device %s", portURI, ingressDeviceId.uri()); | |
return; | |
} | |
} | |
if (flowLiveType != null) { | |
flowLiveType = flowLiveType.toUpperCase(); | |
} | |
if (instructionType != null) { | |
instructionType = instructionType.toUpperCase(); | |
} | |
// convert String to FlowLiveType and check validity | |
TypedStoredFlowEntry.FlowLiveType inLiveType; | |
if (flowLiveType == null) { | |
inLiveType = null; | |
} else { | |
inLiveType = getFlowLiveType(flowLiveType); | |
if (inLiveType == null) { | |
error("Invalid flow live type [%s] error", flowLiveType); | |
return; | |
} | |
} | |
// convert String to InstructionType and check validity | |
Instruction.Type inInstructionType; | |
if (instructionType == null) { | |
inInstructionType = null; | |
} else { | |
inInstructionType = getInstructionType(instructionType); | |
if (inInstructionType == null) { | |
error("Invalid instruction type [%s] error", instructionType); | |
return; | |
} | |
} | |
if (showTopn != null) { | |
int topn = Integer.parseInt(showTopn); | |
if (topn <= 0) { | |
topn = 100; //default value | |
} else if (topn > 1000) { | |
topn = 1000; //max value | |
} | |
// print show topn head line with type | |
print("deviceId=%s, show TOPN=%s flows, live type=%s, instruction type=%s", | |
deviceURI, | |
Integer.toString(topn), | |
flowLiveType == null ? "ALL" : flowLiveType, | |
instructionType == null ? "ALL" : instructionType); | |
if (ingressPortNumber == null) { | |
Map<ConnectPoint, List<TypedFlowEntryWithLoad>> typedFlowLoadMap = | |
flowStatsService.loadTopnByType(device, inLiveType, inInstructionType, topn); | |
// print all ports topn flows load for a given device | |
for (ConnectPoint cp : typedFlowLoadMap.keySet()) { | |
printPortFlowsLoad(cp, typedFlowLoadMap.get(cp)); | |
} | |
} else { | |
List<TypedFlowEntryWithLoad> typedFlowLoad = | |
flowStatsService.loadTopnByType(device, ingressPortNumber, inLiveType, inInstructionType, topn); | |
// print device/port topn flows load | |
ConnectPoint cp = new ConnectPoint(ingressDeviceId, ingressPortNumber); | |
printPortFlowsLoad(cp, typedFlowLoad); | |
} | |
} else if (showAll) { // is true? | |
// print show all head line with type | |
print("deviceId=%s, show ALL flows, live type=%s, instruction type=%s", | |
deviceURI, | |
flowLiveType == null ? "ALL" : flowLiveType, | |
instructionType == null ? "ALL" : instructionType); | |
if (ingressPortNumber == null) { | |
Map<ConnectPoint, List<TypedFlowEntryWithLoad>> typedFlowLoadMap = | |
flowStatsService.loadAllByType(device, inLiveType, inInstructionType); | |
// print all ports all flows load for a given device | |
for (ConnectPoint cp : typedFlowLoadMap.keySet()) { | |
printPortFlowsLoad(cp, typedFlowLoadMap.get(cp)); | |
} | |
} else { | |
List<TypedFlowEntryWithLoad> typedFlowLoad = | |
flowStatsService.loadAllByType(device, ingressPortNumber, inLiveType, inInstructionType); | |
// print device/port all flows load | |
ConnectPoint cp = new ConnectPoint(ingressDeviceId, ingressPortNumber); | |
printPortFlowsLoad(cp, typedFlowLoad); | |
} | |
} else { // if (showSummary == true) //always is true | |
// print show summary head line | |
print("deviceId=%s, show SUMMARY flows", deviceURI); | |
if (ingressPortNumber == null) { | |
Map<ConnectPoint, SummaryFlowEntryWithLoad> summaryFlowLoadMap = | |
flowStatsService.loadSummary(device); | |
// print all ports flow load summary for a given device | |
for (ConnectPoint cp : summaryFlowLoadMap.keySet()) { | |
printPortSummaryLoad(cp, summaryFlowLoadMap.get(cp)); | |
} | |
} else { | |
SummaryFlowEntryWithLoad summaryFlowLoad = | |
flowStatsService.loadSummary(device, ingressPortNumber); | |
// print device/port flow load summary | |
ConnectPoint cp = new ConnectPoint(ingressDeviceId, ingressPortNumber); | |
printPortSummaryLoad(cp, summaryFlowLoad); | |
} | |
} | |
} | |
/** | |
* Extracts the port number portion of the ConnectPoint. | |
* | |
* @param deviceString string representing the device/port | |
* @return port number as a string, empty string if the port is not found | |
*/ | |
private String getPortNumber(String deviceString) { | |
if (deviceString == null) { | |
return ""; | |
} | |
int slash = deviceString.indexOf('/'); | |
if (slash <= 0) { | |
return ""; // return when no port number | |
} | |
return deviceString.substring(slash + 1, deviceString.length()); | |
} | |
/** | |
* Extracts the device ID portion of the ConnectPoint. | |
* | |
* @param deviceString string representing the device/port | |
* @return device ID string | |
*/ | |
private String getDeviceId(String deviceString) { | |
if (deviceString == null) { | |
return ""; | |
} | |
int slash = deviceString.indexOf('/'); | |
if (slash <= 0) { | |
return deviceString; // return only included device ID | |
} | |
return deviceString.substring(0, slash); | |
} | |
/** | |
* converts string of flow live type to FloeLiveType enum. | |
* | |
* @param liveType string representing the flow live type | |
* @return TypedStoredFlowEntry.FlowLiveType | |
*/ | |
private TypedStoredFlowEntry.FlowLiveType getFlowLiveType(String liveType) { | |
String liveTypeUC = liveType.toUpperCase(); | |
if (liveTypeUC.equals("IMMEDIATE")) { | |
return TypedStoredFlowEntry.FlowLiveType.IMMEDIATE_FLOW; | |
} else if (liveTypeUC.equals("SHORT")) { | |
return TypedStoredFlowEntry.FlowLiveType.SHORT_FLOW; | |
} else if (liveTypeUC.equals("MID")) { | |
return TypedStoredFlowEntry.FlowLiveType.MID_FLOW; | |
} else if (liveTypeUC.equals("LONG")) { | |
return TypedStoredFlowEntry.FlowLiveType.LONG_FLOW; | |
} else if (liveTypeUC.equals("UNKNOWN")) { | |
return TypedStoredFlowEntry.FlowLiveType.UNKNOWN_FLOW; | |
} else { | |
return null; // flow live type error | |
} | |
} | |
/** | |
* converts string of instruction type to Instruction type enum. | |
* | |
* @param instType string representing the instruction type | |
* @return Instruction.Type | |
*/ | |
private Instruction.Type getInstructionType(String instType) { | |
String instTypeUC = instType.toUpperCase(); | |
if (instTypeUC.equals("DROP")) { | |
return Instruction.Type.DROP; | |
} else if (instTypeUC.equals("OUTPUT")) { | |
return Instruction.Type.OUTPUT; | |
} else if (instTypeUC.equals("GROUP")) { | |
return Instruction.Type.GROUP; | |
} else if (instTypeUC.equals("L0MODIFICATION")) { | |
return Instruction.Type.L0MODIFICATION; | |
} else if (instTypeUC.equals("L2MODIFICATION")) { | |
return Instruction.Type.L2MODIFICATION; | |
} else if (instTypeUC.equals("TABLE")) { | |
return Instruction.Type.TABLE; | |
} else if (instTypeUC.equals("L3MODIFICATION")) { | |
return Instruction.Type.L3MODIFICATION; | |
} else if (instTypeUC.equals("METADATA")) { | |
return Instruction.Type.METADATA; | |
} else { | |
return null; // instruction type error | |
} | |
} | |
private void printPortFlowsLoad(ConnectPoint cp, List<TypedFlowEntryWithLoad> typedFlowLoad) { | |
print(" deviceId/Port=%s/%s, %s flows", cp.elementId(), cp.port(), typedFlowLoad.size()); | |
for (TypedFlowEntryWithLoad tfel: typedFlowLoad) { | |
TypedStoredFlowEntry tfe = tfel.typedStoredFlowEntry(); | |
print(" flowId=%s, state=%s, liveType=%s, life=%s -> %s", | |
Long.toHexString(tfe.id().value()), | |
tfe.state(), | |
tfe.flowLiveType(), | |
tfe.life(), | |
tfel.load().isValid() ? tfel.load() : "Load{rate=0, NOT VALID}"); | |
} | |
} | |
private void printPortSummaryLoad(ConnectPoint cp, SummaryFlowEntryWithLoad summaryFlowLoad) { | |
print(" deviceId/Port=%s/%s, Total=%s, Immediate=%s, Short=%s, Mid=%s, Long=%s, Unknown=%s", | |
cp.elementId(), | |
cp.port(), | |
summaryFlowLoad.totalLoad().isValid() ? summaryFlowLoad.totalLoad() : "Load{rate=0, NOT VALID}", | |
summaryFlowLoad.immediateLoad().isValid() ? summaryFlowLoad.immediateLoad() : "Load{rate=0, NOT VALID}", | |
summaryFlowLoad.shortLoad().isValid() ? summaryFlowLoad.shortLoad() : "Load{rate=0, NOT VALID}", | |
summaryFlowLoad.midLoad().isValid() ? summaryFlowLoad.midLoad() : "Load{rate=0, NOT VALID}", | |
summaryFlowLoad.longLoad().isValid() ? summaryFlowLoad.longLoad() : "Load{rate=0, NOT VALID}", | |
summaryFlowLoad.unknownLoad().isValid() ? summaryFlowLoad.unknownLoad() : "Load{rate=0, NOT VALID}"); | |
} | |
} |