From 9c6ac1db63e54e753ebdc2aa3fc73c68ef2a2247 Mon Sep 17 00:00:00 2001 From: Michal Cmarada Date: Fri, 3 May 2019 11:29:04 +0200 Subject: Implement callbacks and registry for stats Change-Id: I9e562482195fcd5f54634cfff29df4358b78ed28 Signed-off-by: Michal Cmarada --- .../io/fd/jvpp/stats/JVppClientStatsImpl.java | 90 -------- java/jvpp-stats/io/fd/jvpp/stats/JVppStats.java | 31 +++ .../jvpp-stats/io/fd/jvpp/stats/JVppStatsImpl.java | 121 +++++++++++ .../io/fd/jvpp/stats/JVppStatsRegistryImpl.java | 96 +++++++++ .../io/fd/jvpp/stats/VppStatsJNIConnection.java | 142 +++++++++++++ .../InterfaceStatisticsDetailsCallback.java | 25 +++ .../stats/callback/JVppStatsGlobalCallback.java | 23 ++ .../jvpp/stats/callfacade/CallbackJVppStats.java | 28 +++ .../stats/callfacade/CallbackJVppStatsFacade.java | 61 ++++++ .../CallbackJVppStatsFacadeCallback.java | 67 ++++++ .../io/fd/jvpp/stats/dto/InterfaceStatistics.java | 101 ++++----- .../jvpp/stats/dto/InterfaceStatisticsDetails.java | 71 +++++++ .../dto/InterfaceStatisticsDetailsReplyDump.java | 54 +++++ .../fd/jvpp/stats/dto/InterfaceStatisticsDump.java | 51 +++++ .../stats/future/AbstractFutureJVppInvoker.java | 170 +++++++++++++++ .../io/fd/jvpp/stats/future/FutureJVppInvoker.java | 48 +++++ .../io/fd/jvpp/stats/future/FutureJVppStats.java | 28 +++ .../jvpp/stats/future/FutureJVppStatsFacade.java | 53 +++++ .../future/FutureJVppStatsFacadeCallback.java | 81 +++++++ .../io/fd/jvpp/stats/test/FutureApiTest.java | 62 ++++++ .../io/fd/jvpp/stats/test/JvppStatsApiTest.java | 44 ---- java/jvpp-stats/io/fd/jvpp/stats/test/Readme.txt | 9 +- java/jvpp-stats/jvpp_interface_stats.h | 236 +++++++++++++++++++++ java/jvpp-stats/jvpp_stats.c | 161 ++++++-------- java/jvpp-stats/jvpp_stats.h | 44 ++-- java/jvpp-stats/jvpp_stats_registry.c | 214 +++++++++++++++++++ 26 files changed, 1801 insertions(+), 310 deletions(-) delete mode 100644 java/jvpp-stats/io/fd/jvpp/stats/JVppClientStatsImpl.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/JVppStats.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/JVppStatsImpl.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/JVppStatsRegistryImpl.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/VppStatsJNIConnection.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/callback/InterfaceStatisticsDetailsCallback.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/callback/JVppStatsGlobalCallback.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStats.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacade.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacadeCallback.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetails.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetailsReplyDump.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDump.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/future/AbstractFutureJVppInvoker.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppInvoker.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStats.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacade.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacadeCallback.java create mode 100644 java/jvpp-stats/io/fd/jvpp/stats/test/FutureApiTest.java delete mode 100644 java/jvpp-stats/io/fd/jvpp/stats/test/JvppStatsApiTest.java create mode 100644 java/jvpp-stats/jvpp_interface_stats.h create mode 100644 java/jvpp-stats/jvpp_stats_registry.c (limited to 'java/jvpp-stats') diff --git a/java/jvpp-stats/io/fd/jvpp/stats/JVppClientStatsImpl.java b/java/jvpp-stats/io/fd/jvpp/stats/JVppClientStatsImpl.java deleted file mode 100644 index 32d2345..0000000 --- a/java/jvpp-stats/io/fd/jvpp/stats/JVppClientStatsImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2019 PANTHEON.tech. - * - * 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 io.fd.jvpp.stats; - -import static io.fd.jvpp.NativeLibraryLoader.loadLibrary; -import static java.lang.String.format; - -import io.fd.jvpp.stats.dto.InterfaceStatistics; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.LinkedList; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class JVppClientStatsImpl implements AutoCloseable { - - private boolean connected; - - private static final Logger LOG = Logger.getLogger(JVppClientStatsImpl.class.getName()); - - static { - final String libName = "libjvpp_stats.so"; - try { - loadLibrary(libName, JVppClientStatsImpl.class); - } catch (IOException e) { - LOG.log(Level.SEVERE, format("Can't find vpp jni library: %s", libName), e); - throw new ExceptionInInitializerError(e); - } - } - - private static native InterfaceStatistics[] interfaceStatisticsDump(); - private static native int statSegmentConnect(); - private static native void statSegmentDisconnect(); - - public synchronized List getInterfaceStatistics() { - if (!this.connected) { - LOG.severe("Unable to dump statistics. Client isn't connected. Try reconnecting."); - return null; - } - InterfaceStatistics[] statDump = interfaceStatisticsDump(); - List statistics = new LinkedList(); - if (statDump != null) { - statistics = Arrays.asList(statDump); - } - return statistics; - } - - public JVppClientStatsImpl() { - connectClient(); - } - - public boolean reconnect() { - connectClient(); - return this.connected; - } - - private void connectClient() { - if (!this.connected) { - if (statSegmentConnect() == 0) { - this.connected = true; - } - } - } - - public boolean isConnected() { - return connected; - } - - public void close() { - if (this.connected) { - statSegmentDisconnect(); - this.connected = false; - } - } -} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/JVppStats.java b/java/jvpp-stats/io/fd/jvpp/stats/JVppStats.java new file mode 100644 index 0000000..c4f22dc --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/JVppStats.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats; + +/** + *

Java representation of plugin's api file. + */ +public interface JVppStats extends io.fd.jvpp.JVpp { + /** + * Generic dispatch method for sending requests to VPP + * + * @throws io.fd.jvpp.VppInvocationException if send request had failed + */ + int send(io.fd.jvpp.dto.JVppRequest request) throws io.fd.jvpp.VppInvocationException; + + int interfaceStatisticsDump() throws io.fd.jvpp.VppInvocationException; +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/JVppStatsImpl.java b/java/jvpp-stats/io/fd/jvpp/stats/JVppStatsImpl.java new file mode 100644 index 0000000..fdee289 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/JVppStatsImpl.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats; + +import io.fd.jvpp.JVppRegistry; +import io.fd.jvpp.VppConnection; +import io.fd.jvpp.VppInvocationException; +import io.fd.jvpp.callback.JVppCallback; +import io.fd.jvpp.dto.ControlPing; +import io.fd.jvpp.dto.JVppRequest; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.logging.Logger; + +public class JVppStatsImpl implements io.fd.jvpp.stats.JVppStats { + + private final static Logger LOG = Logger.getLogger(JVppStatsImpl.class.getName()); + private static final java.lang.String LIBNAME = "libjvpp_stats.so"; + + static { + try { + loadLibrary(); + } catch (Exception e) { + LOG.severe("Can't find jvpp jni library: " + LIBNAME); + throw new ExceptionInInitializerError(e); + } + } + + private VppConnection connection; + private JVppRegistry registry; + + private static void loadStream(final InputStream is) throws IOException { + final Set perms = PosixFilePermissions.fromString("rwxr-x---"); + final Path p = Files.createTempFile(LIBNAME, null, PosixFilePermissions.asFileAttribute(perms)); + try { + Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); + + try { + Runtime.getRuntime().load(p.toString()); + } catch (UnsatisfiedLinkError e) { + throw new IOException("Failed to load library " + p, e); + } + } finally { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + } + } + } + + private static void loadLibrary() throws IOException { + try (final InputStream is = JVppStatsImpl.class.getResourceAsStream('/' + LIBNAME)) { + if (is == null) { + throw new IOException("Failed to open library resource " + LIBNAME); + } + loadStream(is); + } + } + + private static native void init0(final JVppCallback callback, final long queueAddress, final int clientIndex); + + private static native int interfaceStatisticsDump0() throws io.fd.jvpp.VppInvocationException; + + private static native void close0(); + + @Override + public int send(final JVppRequest request) throws VppInvocationException { + return request.send(this); + } + + @Override + public void init(final JVppRegistry registry, final JVppCallback callback, final long queueAddress, + final int clientIndex) { + this.registry = java.util.Objects.requireNonNull(registry, "registry should not be null"); + this.connection = java.util.Objects.requireNonNull(registry.getConnection(), "connection should not be null"); + connection.checkActive(); + init0(callback, queueAddress, clientIndex); + } + + @Override + public int controlPing(final ControlPing controlPing) { + return 1; + } + + @Override + public int interfaceStatisticsDump() throws VppInvocationException { + connection.checkActive(); + LOG.fine("Sending interfaceStatisticsDump event message"); + int result = interfaceStatisticsDump0(); + if (result < 0) { + throw new io.fd.jvpp.VppInvocationException("interfaceStatisticsDump", result); + } + return result; + } + + @Override + public void close() { + close0(); + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/JVppStatsRegistryImpl.java b/java/jvpp-stats/io/fd/jvpp/stats/JVppStatsRegistryImpl.java new file mode 100644 index 0000000..a94a77f --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/JVppStatsRegistryImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats; + +import io.fd.jvpp.JVpp; +import io.fd.jvpp.JVppRegistry; +import io.fd.jvpp.VppInvocationException; +import io.fd.jvpp.callback.JVppCallback; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.Objects.requireNonNull; + +/** + * Stats implementation of JVppRegistry. + */ +public final class JVppStatsRegistryImpl implements JVppRegistry { + + private final VppStatsJNIConnection connection; + // Unguarded concurrent map, no race conditions expected on top of that + private final Map pluginRegistry; + + public JVppStatsRegistryImpl(final String clientName) throws IOException { + connection = new VppStatsJNIConnection(clientName); + connection.connect(); + pluginRegistry = new ConcurrentHashMap<>(); + } + + @Override + public VppStatsJNIConnection getConnection() { + return connection; + } + + @Override + public void register(final JVpp jvpp, final JVppCallback callback) { + requireNonNull(jvpp, "jvpp should not be null"); + requireNonNull(callback, "Callback should not be null"); + final String name = jvpp.getClass().getName(); + if (pluginRegistry.containsKey(name)) { + throw new IllegalArgumentException( + String.format("Callback for plugin %s was already registered", name)); + } + jvpp.init(this, callback, connection.getStatsConnectionInfo().queueAddress, + connection.getStatsConnectionInfo().clientIndex); + pluginRegistry.put(name, callback); + } + + @Override + public void unregister(final String name) { + requireNonNull(name, "Plugin name should not be null"); + final JVppCallback previous = pluginRegistry.remove(name); + assertPluginWasRegistered(name, previous); + } + + @Override + public JVppCallback get(final String name) { + requireNonNull(name, "Plugin name should not be null"); + JVppCallback value = pluginRegistry.get(name); + assertPluginWasRegistered(name, value); + return value; + } + + private native int controlPing0() throws VppInvocationException; + + @Override + public int controlPing(Class clazz) throws VppInvocationException { + return controlPing0(); + } + + private static void assertPluginWasRegistered(final String name, final JVppCallback value) { + if (value == null) { + throw new IllegalArgumentException(String.format("Callback for plugin %s is not registered", name)); + } + } + + @Override + public void close() throws Exception { + connection.close(); + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/VppStatsJNIConnection.java b/java/jvpp-stats/io/fd/jvpp/stats/VppStatsJNIConnection.java new file mode 100644 index 0000000..164a553 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/VppStatsJNIConnection.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats; + +import io.fd.jvpp.VppConnection; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static io.fd.jvpp.NativeLibraryLoader.loadLibrary; +import static java.lang.String.format; + +/** + * JNI based representation of a management connection to VPP. + */ +public final class VppStatsJNIConnection implements VppConnection { + private static final Logger LOG = Logger.getLogger(VppStatsJNIConnection.class.getName()); + + private StatsConnectionInfo connectionInfo; + + private final String clientName; + private volatile boolean disconnected = false; + + static { + final String libName = "libjvpp_stats_registry.so"; + try { + loadLibrary(libName, VppStatsJNIConnection.class); + } catch (IOException e) { + LOG.log(Level.SEVERE, format("Can't find vpp jni library: %s", libName), e); + throw new ExceptionInInitializerError(e); + } + } + + /** + * Create VPPStatsJNIConnection instance for client connecting to VPP. + * + * @param clientName client name instance to be used for communication. Single connection per clientName is + * allowed. + */ + public VppStatsJNIConnection(final String clientName) { + this.clientName = Objects.requireNonNull(clientName, "Null clientName"); + } + + /** + * Guarded by VppStatsJNIConnection.class + */ + private static final Map connections = new HashMap<>(); + + /** + * Initiate VPP connection for current instance + *

+ * Multiple instances are allowed since this class is not a singleton (VPP allows multiple management connections). + *

+ * However only a single connection per clientName is allowed. + * + * @throws IOException in case the connection could not be established + */ + + @Override + public void connect() throws IOException { + _connect(); + } + + private void _connect() throws IOException { + + synchronized (VppStatsJNIConnection.class) { + if (connections.containsKey(clientName)) { + throw new IOException("Client " + clientName + " already connected"); + } + + connectionInfo = statsConnect(clientName); + if (connectionInfo.status != 0) { + throw new IOException("Connection returned error " + connectionInfo.status); + } + connections.put(clientName, this); + } + } + + @Override + public final void checkActive() { + if (disconnected) { + throw new IllegalStateException("Disconnected client " + clientName); + } + } + + @Override + public final synchronized void close() { + if (!disconnected) { + disconnected = true; + try { + statsDisconnect(); + } finally { + synchronized (VppStatsJNIConnection.class) { + connections.remove(clientName); + } + } + } + } + + public StatsConnectionInfo getStatsConnectionInfo() { + return connectionInfo; + } + + /** + * VPP connection information used by plugins to reuse the connection. + */ + public static final class StatsConnectionInfo { + public final long queueAddress; + public final int clientIndex; + public final int status; // FIXME throw exception instead + public final int pid; + + public StatsConnectionInfo(long queueAddress, int clientIndex, int status, int pid) { + this.queueAddress = queueAddress; + this.clientIndex = clientIndex; + this.status = status; + this.pid = pid; + } + } + + private static native StatsConnectionInfo statsConnect(String clientName); + + private static native void statsDisconnect(); +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/callback/InterfaceStatisticsDetailsCallback.java b/java/jvpp-stats/io/fd/jvpp/stats/callback/InterfaceStatisticsDetailsCallback.java new file mode 100644 index 0000000..86fe189 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/callback/InterfaceStatisticsDetailsCallback.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.callback; + +/** + *

Represents callback for plugin's api message. + */ +public interface InterfaceStatisticsDetailsCallback extends io.fd.jvpp.callback.JVppCallback { + + void onInterfaceStatisticsDetails(io.fd.jvpp.stats.dto.InterfaceStatisticsDetails reply); +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/callback/JVppStatsGlobalCallback.java b/java/jvpp-stats/io/fd/jvpp/stats/callback/JVppStatsGlobalCallback.java new file mode 100644 index 0000000..794a645 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/callback/JVppStatsGlobalCallback.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.callback; + +/** + * Global aggregated callback interface. + */ +public interface JVppStatsGlobalCallback extends io.fd.jvpp.stats.callback.InterfaceStatisticsDetailsCallback { +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStats.java b/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStats.java new file mode 100644 index 0000000..307e24d --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStats.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.callfacade; + +import io.fd.jvpp.stats.callback.InterfaceStatisticsDetailsCallback; + +/** + *

Callback Java API representation of io.fd.jvpp.stats plugin. + */ +public interface CallbackJVppStats extends io.fd.jvpp.notification.EventRegistryProvider, AutoCloseable { + + // TODO add send + void interfaceStatisticsDump(InterfaceStatisticsDetailsCallback callback) throws io.fd.jvpp.VppInvocationException; +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacade.java b/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacade.java new file mode 100644 index 0000000..f936dc1 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacade.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.callfacade; + +import io.fd.jvpp.notification.EventRegistry; + +/** + *

Default implementation of CallbackStatsJVpp interface. + */ +public final class CallbackJVppStatsFacade implements CallbackJVppStats { + + private final io.fd.jvpp.stats.JVppStats jvpp; + private final java.util.Map callbacks; + + /** + *

Create CallbackJVppStatsFacade object for provided JVpp instance. + * Constructor internally creates CallbackJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided io.fd.jvpp.JVpp instance + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public CallbackJVppStatsFacade(final io.fd.jvpp.JVppRegistry registry, final io.fd.jvpp.stats.JVppStats jvpp) + throws java.io.IOException { + this.jvpp = java.util.Objects.requireNonNull(jvpp, "jvpp is null"); + this.callbacks = new java.util.HashMap<>(); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new CallbackJVppStatsFacadeCallback(this.callbacks)); + } + + @Override + public void close() throws Exception { + jvpp.close(); + } + + public final void interfaceStatisticsDump(io.fd.jvpp.stats.callback.InterfaceStatisticsDetailsCallback callback) + throws io.fd.jvpp.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.interfaceStatisticsDump(), callback); + } + } + + @Override + public EventRegistry getEventRegistry() { + return null; + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacadeCallback.java b/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacadeCallback.java new file mode 100644 index 0000000..ec1feb2 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/callfacade/CallbackJVppStatsFacadeCallback.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.callfacade; + +/** + *

Implementation of JVppGlobalCallback interface for Java Callback API. + */ +public final class CallbackJVppStatsFacadeCallback implements io.fd.jvpp.stats.callback.JVppStatsGlobalCallback { + + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger( + CallbackJVppStatsFacadeCallback.class.getName()); + private final java.util.Map requests; + + public CallbackJVppStatsFacadeCallback(final java.util.Map requestMap) { + this.requests = requestMap; + } + + @Override + public void onError(io.fd.jvpp.VppCallbackException reply) { + + io.fd.jvpp.callback.JVppCallback failedCall; + synchronized (requests) { + failedCall = requests.remove(reply.getCtxId()); + } + + if (failedCall != null) { + try { + failedCall.onError(reply); + } catch (RuntimeException ex) { + ex.addSuppressed(reply); + LOG.log(java.util.logging.Level.WARNING, + String.format("Callback: %s failed while handling exception: %s", failedCall, reply), ex); + } + } + } + + @Override + public void onInterfaceStatisticsDetails(final io.fd.jvpp.stats.dto.InterfaceStatisticsDetails reply) { + + io.fd.jvpp.stats.callback.InterfaceStatisticsDetailsCallback callback; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received InterfaceStatisticsDetails event message: %s", reply)); + } + synchronized (requests) { + callback = (io.fd.jvpp.stats.callback.InterfaceStatisticsDetailsCallback) requests.remove(replyId); + } + + if (callback != null) { + callback.onInterfaceStatisticsDetails(reply); + } + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatistics.java b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatistics.java index 49486a1..e831e35 100644 --- a/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatistics.java +++ b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatistics.java @@ -16,25 +16,27 @@ package io.fd.jvpp.stats.dto; +import java.util.Objects; + public class InterfaceStatistics { - private int swIfIndex; - private int outErrors; - private int outMulticastPkts; - private int outUnicastPkts; - private int outBroadcastPkts; - private int outBytes; - private int inErrors; - private int inMulticastPkts; - private int inUnicastPkts; - private int inBroadcastPkts; - private int inBytes; + public int swIfIndex; + public int outErrors; + public int outMulticastPkts; + public int outUnicastPkts; + public int outBroadcastPkts; + public int outBytes; + public int inErrors; + public int inMulticastPkts; + public int inUnicastPkts; + public int inBroadcastPkts; + public int inBytes; public InterfaceStatistics(final int swIfIndex, final int outErrors, final int outMulticastPkts, - final int outUnicastPkts, - final int outBroadcastPkts, final int outBytes, final int inErrors, - final int inMulticastPkts, final int inUnicastPkts, - final int inBroadcastPkts, final int inBytes) { + final int outUnicastPkts, final int outBroadcastPkts, + final int outBytes, final int inErrors, final int inMulticastPkts, + final int inUnicastPkts, final int inBroadcastPkts, + final int inBytes) { this.swIfIndex = swIfIndex; this.outErrors = outErrors; this.outMulticastPkts = outMulticastPkts; @@ -48,52 +50,37 @@ public class InterfaceStatistics { this.inBytes = inBytes; } - public int getSwIfIndex() { - return swIfIndex; - } - - public int getOutErrors() { - return outErrors; - } - - public int getOutMulticastPkts() { - return outMulticastPkts; - } - - public int getOutUnicastPkts() { - return outUnicastPkts; - } - - public int getOutBroadcastPkts() { - return outBroadcastPkts; - } - - public int getOutBytes() { - return outBytes; - } - - public int getInErrors() { - return inErrors; - } - - public int getInMulticastPkts() { - return inMulticastPkts; - } - - public int getInUnicastPkts() { - return inUnicastPkts; - } - - public int getInBroadcastPkts() { - return inBroadcastPkts; + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + final InterfaceStatistics otherIfcStats = (InterfaceStatistics) other; + return swIfIndex == otherIfcStats.swIfIndex && + outErrors == otherIfcStats.outErrors && + outMulticastPkts == otherIfcStats.outMulticastPkts && + outUnicastPkts == otherIfcStats.outUnicastPkts && + outBroadcastPkts == otherIfcStats.outBroadcastPkts && + outBytes == otherIfcStats.outBytes && + inErrors == otherIfcStats.inErrors && + inMulticastPkts == otherIfcStats.inMulticastPkts && + inUnicastPkts == otherIfcStats.inUnicastPkts && + inBroadcastPkts == otherIfcStats.inBroadcastPkts && + inBytes == otherIfcStats.inBytes; } - public int getInBytes() { - return inBytes; + @Override + public int hashCode() { + return Objects + .hash(swIfIndex, outErrors, outMulticastPkts, outUnicastPkts, outBroadcastPkts, outBytes, inErrors, + inMulticastPkts, inUnicastPkts, inBroadcastPkts, inBytes); } - @java.lang.Override - public java.lang.String toString() { + @Override + public String toString() { return "InterfaceStatistics{" + "swIfIndex=" + swIfIndex + ", outErrors=" + outErrors + diff --git a/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetails.java b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetails.java new file mode 100644 index 0000000..997dc70 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetails.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.dto; + +/** + *

This class represents reply DTO for InterfaceStatisticsDetails. + */ +public final class InterfaceStatisticsDetails implements io.fd.jvpp.dto.JVppReply { + public int context; + public InterfaceStatistics[] interfaceStatistics; + public int length; + + public InterfaceStatisticsDetails(int length, int context) { + this.context = context; + this.length = length; + this.interfaceStatistics = new InterfaceStatistics[length]; + } + + @Override + @io.fd.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash(context, interfaceStatistics, length); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final InterfaceStatisticsDetails other = (InterfaceStatisticsDetails) o; + + if (!java.util.Objects.equals(this.context, other.context)) { + return false; + } + if (!java.util.Arrays.equals(this.interfaceStatistics, other.interfaceStatistics)) { + return false; + } + if (!java.util.Objects.equals(this.length, other.length)) { + return false; + } else { + return true; + } + } + + @Override + public java.lang.String toString() { + return "InterfaceStatisticsDetails{" + + "context=" + context + + ", interfaceStatistics=" + java.util.Arrays.toString(interfaceStatistics) + + ", length=" + length + + '}'; + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetailsReplyDump.java b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetailsReplyDump.java new file mode 100644 index 0000000..53ef30f --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDetailsReplyDump.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.dto; + +/** + *

This class represents dump reply wrapper for InterfaceStatisticsDetails. + */ +public final class InterfaceStatisticsDetailsReplyDump + implements io.fd.jvpp.dto.JVppReplyDump { + + public InterfaceStatisticsDetails interfaceStatisticsDetails; + + @Override + @io.fd.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash(interfaceStatisticsDetails); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final InterfaceStatisticsDetailsReplyDump + other = (InterfaceStatisticsDetailsReplyDump) o; + + return java.util.Objects.equals(this.interfaceStatisticsDetails, other.interfaceStatisticsDetails); + } + + @Override + public String toString() { + return "InterfaceStatisticsDetailsReplyDump{" + + "interfaceStatisticsDetails=" + interfaceStatisticsDetails + + '}'; + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDump.java b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDump.java new file mode 100644 index 0000000..e51936a --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/dto/InterfaceStatisticsDump.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.dto; + +/** + *

This class represents request DTO. + */ +public final class InterfaceStatisticsDump implements io.fd.jvpp.dto.JVppDump { + + @Override + @io.fd.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return true; + } + + @Override + public String toString() { + return "InterfaceStatisticsDump{}"; + } + + @Override + public int send(final io.fd.jvpp.JVpp jvpp) throws io.fd.jvpp.VppInvocationException { + return ((io.fd.jvpp.stats.JVppStats) jvpp).interfaceStatisticsDump(); + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/future/AbstractFutureJVppInvoker.java b/java/jvpp-stats/io/fd/jvpp/stats/future/AbstractFutureJVppInvoker.java new file mode 100644 index 0000000..e66cfd8 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/future/AbstractFutureJVppInvoker.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.future; + + +import io.fd.jvpp.JVpp; +import io.fd.jvpp.JVppRegistry; +import io.fd.jvpp.VppInvocationException; +import io.fd.jvpp.dto.JVppDump; +import io.fd.jvpp.dto.JVppReply; +import io.fd.jvpp.dto.JVppReplyDump; +import io.fd.jvpp.dto.JVppRequest; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +/** + * Future facade on top of JVpp + */ +public abstract class AbstractFutureJVppInvoker implements FutureJVppInvoker { + + private final JVpp jvpp; + private final JVppRegistry registry; + + /** + * Guarded by self + */ + private final Map>> requests; + + protected AbstractFutureJVppInvoker(final JVpp jvpp, final JVppRegistry registry, + final Map>> requestMap) { + this.jvpp = Objects.requireNonNull(jvpp, "jvpp should not be null"); + this.registry = Objects.requireNonNull(registry, "registry should not be null"); + // Request map represents the shared state between this facade and it's callback + // where facade puts futures in and callback completes + removes them + this.requests = Objects.requireNonNull(requestMap, "Null requestMap"); + } + + protected final Map>> getRequests() { + synchronized (requests) { + return requests; + } + } + + // TODO use Optional in Future, java8 + + @Override + @SuppressWarnings("unchecked") + public > CompletionStage send(REQ req) { + try { + // jvpp.send() can go to waiting state if sending queue is full, putting it into same + // synchronization block as used by receiving part (synchronized(requests)) can lead + // to deadlock between these two sides or at least slowing sending process by slow + // reader + final CompletableFuture replyCompletableFuture; + final int contextId = jvpp.send(req); + + if(req instanceof JVppDump) { + throw new IllegalArgumentException("Send with empty reply dump has to be used in case of dump calls"); + } + + synchronized(requests) { + CompletableFuture> replyFuture = requests.get(contextId); + if (replyFuture == null) { + // reply not yet received, put new future into map + replyCompletableFuture = new CompletableFuture<>(); + requests.put(contextId, replyCompletableFuture); + } else { + // reply already received (should be completed by reader), + // remove future from map and return it to caller + replyCompletableFuture = (CompletableFuture) replyFuture; + requests.remove(contextId); + } + } + + // TODO in case of timeouts/missing replies, requests from the map are not removed + // consider adding cancel method, that would remove requests from the map and cancel + // associated replyCompletableFuture + + return replyCompletableFuture; + } catch (VppInvocationException ex) { + final CompletableFuture replyCompletableFuture = new CompletableFuture<>(); + replyCompletableFuture.completeExceptionally(ex); + return replyCompletableFuture; + } + } + + @Override + @SuppressWarnings("unchecked") + public , DUMP extends JVppReplyDump> CompletionStage send( + REQ req, DUMP emptyReplyDump) { + try { + // jvpp.send() can go to waiting state if sending queue is full, + // putting it into same synchronization block as used by receiving part (synchronized(requests)) + // can lead to deadlock between these two sides or at least slowing sending process by slow reader + final CompletableDumpFuture replyDumpFuture; + final int contextId = jvpp.send(req); + + if(!(req instanceof JVppDump)) { + throw new IllegalArgumentException("Send without empty reply dump has to be used in case of regular calls"); + } + + synchronized(requests) { + CompletableFuture> replyFuture = requests.get(contextId); + if (replyFuture == null) { + // reply not received yet, put new future to map + replyDumpFuture = new CompletableDumpFuture<>(contextId, emptyReplyDump); + requests.put(contextId, replyDumpFuture); + } else { + // reply already received, save existing future + replyDumpFuture = (CompletableDumpFuture) replyFuture; + } + } + + synchronized (requests) { + // reply already received, complete future + replyDumpFuture.complete(replyDumpFuture.getReplyDump()); + requests.remove(contextId); + } + + // TODO in case of timeouts/missing replies, requests from the map are not removed + // consider adding cancel method, that would remove requests from the map and cancel + // associated replyCompletableFuture + + return replyDumpFuture; + } catch (VppInvocationException ex) { + final CompletableFuture replyCompletableFuture = new CompletableFuture<>(); + replyCompletableFuture.completeExceptionally(ex); + return replyCompletableFuture; + } + } + + public static final class CompletableDumpFuture> extends CompletableFuture { + private final T replyDump; + private final int contextId; + + public CompletableDumpFuture(final int contextId, final T emptyDump) { + this.contextId = contextId; + this.replyDump = emptyDump; + } + + public int getContextId() { + return contextId; + } + + public T getReplyDump() { + return replyDump; + } + } + + @Override + public void close() throws Exception { + jvpp.close(); + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppInvoker.java b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppInvoker.java new file mode 100644 index 0000000..c93c332 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppInvoker.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.future; + + +import io.fd.jvpp.dto.JVppReply; +import io.fd.jvpp.dto.JVppReplyDump; +import io.fd.jvpp.dto.JVppRequest; +import io.fd.jvpp.notification.EventRegistryProvider; +import java.util.concurrent.CompletionStage; + +/** +* Future facade on top of JVpp +*/ +public interface FutureJVppInvoker extends EventRegistryProvider, AutoCloseable { + + /** + * Invoke asynchronous operation on VPP + * + * @return CompletionStage with future result of an async VPP call + * @throws io.fd.jvpp.VppInvocationException when send request failed with details + */ + > CompletionStage send(REQ req); + + + /** + * Invoke asynchronous dump operation on VPP + * + * @return CompletionStage with aggregated future result of an async VPP dump call + * @throws io.fd.jvpp.VppInvocationException when send request failed with details + */ + , DUMP extends JVppReplyDump> CompletionStage send( + REQ req, DUMP emptyReplyDump); +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStats.java b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStats.java new file mode 100644 index 0000000..288b556 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStats.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.future; + +import io.fd.jvpp.stats.dto.InterfaceStatisticsDetailsReplyDump; + +/** + *

Async facade extension adding specific methods for each request invocation + */ +public interface FutureJVppStats extends io.fd.jvpp.stats.future.FutureJVppInvoker { + + java.util.concurrent.CompletionStage interfaceStatisticsDump( + io.fd.jvpp.stats.dto.InterfaceStatisticsDump request); +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacade.java b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacade.java new file mode 100644 index 0000000..470a2fd --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacade.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.future; + +import io.fd.jvpp.notification.EventRegistry; +import io.fd.jvpp.stats.dto.InterfaceStatisticsDetailsReplyDump; + +/** + *

Implementation of FutureJVpp based on AbstractFutureJVppInvoker + */ +public class FutureJVppStatsFacade extends io.fd.jvpp.stats.future.AbstractFutureJVppInvoker + implements FutureJVppStats { + + /** + *

Create FutureJVppStatsFacade object for provided JVpp instance. + * Constructor internally creates FutureJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided io.fd.jvpp.JVpp instance + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public FutureJVppStatsFacade(final io.fd.jvpp.JVppRegistry registry, final io.fd.jvpp.JVpp jvpp) + throws java.io.IOException { + super(jvpp, registry, new java.util.HashMap<>()); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new FutureJVppStatsFacadeCallback(getRequests())); + } + + @Override + public java.util.concurrent.CompletionStage interfaceStatisticsDump( + io.fd.jvpp.stats.dto.InterfaceStatisticsDump request) { + return send(request, new InterfaceStatisticsDetailsReplyDump()); + } + + @Override + public EventRegistry getEventRegistry() { + return null; + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacadeCallback.java b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacadeCallback.java new file mode 100644 index 0000000..a215b89 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/future/FutureJVppStatsFacadeCallback.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.future; + +import io.fd.jvpp.stats.dto.InterfaceStatisticsDetailsReplyDump; + +/** + *

Async facade callback setting values to future objects + */ +public final class FutureJVppStatsFacadeCallback implements io.fd.jvpp.stats.callback.JVppStatsGlobalCallback { + + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger( + FutureJVppStatsFacadeCallback.class.getName()); + private final java.util.Map>> + requests; + + public FutureJVppStatsFacadeCallback( + final java.util.Map>> requestMap) { + this.requests = requestMap; + } + + @Override + @SuppressWarnings("unchecked") + public void onError(io.fd.jvpp.VppCallbackException reply) { + final java.util.concurrent.CompletableFuture> completableFuture; + + synchronized (requests) { + completableFuture = (java.util.concurrent.CompletableFuture>) requests + .get(reply.getCtxId()); + } + + if (completableFuture != null) { + completableFuture.completeExceptionally(reply); + + synchronized (requests) { + requests.remove(reply.getCtxId()); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onInterfaceStatisticsDetails(final io.fd.jvpp.stats.dto.InterfaceStatisticsDetails reply) { + io.fd.jvpp.stats.future.AbstractFutureJVppInvoker.CompletableDumpFuture + completableFuture; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received InterfaceStatisticsDetails event message: %s", reply)); + } + synchronized (requests) { + completableFuture = + (io.fd.jvpp.stats.future.AbstractFutureJVppInvoker.CompletableDumpFuture) requests + .get(replyId); + + if (completableFuture == null) { + // reply received before writer created future, + // create new future, and put into map to notify sender that reply is already received, + // following details replies will add information to this future + completableFuture = + new io.fd.jvpp.stats.future.AbstractFutureJVppInvoker.CompletableDumpFuture<>(replyId, + new InterfaceStatisticsDetailsReplyDump()); + requests.put(replyId, completableFuture); + } + completableFuture.getReplyDump().interfaceStatisticsDetails = reply; + } + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/test/FutureApiTest.java b/java/jvpp-stats/io/fd/jvpp/stats/test/FutureApiTest.java new file mode 100644 index 0000000..2b28915 --- /dev/null +++ b/java/jvpp-stats/io/fd/jvpp/stats/test/FutureApiTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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 io.fd.jvpp.stats.test; + + +import io.fd.jvpp.JVppRegistry; +import io.fd.jvpp.stats.JVppStatsImpl; +import io.fd.jvpp.stats.JVppStatsRegistryImpl; +import io.fd.jvpp.stats.dto.InterfaceStatisticsDetailsReplyDump; +import io.fd.jvpp.stats.dto.InterfaceStatisticsDump; +import io.fd.jvpp.stats.future.FutureJVppStatsFacade; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for stats plugin"); + try (final JVppRegistry registry = new JVppStatsRegistryImpl("FutureApiTest"); + final FutureJVppStatsFacade jvpp = new FutureJVppStatsFacade(registry, new JVppStatsImpl())) { + LOG.info("Successfully connected to VPP"); + testinterfaceStatisticsDump(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testinterfaceStatisticsDump(FutureJVppStatsFacade jvpp) throws Exception { + LOG.info("Sending InterfaceStatisticsDump request..."); + final InterfaceStatisticsDump request = new InterfaceStatisticsDump(); + + final Future replyFuture = + jvpp.interfaceStatisticsDump(request).toCompletableFuture(); + final InterfaceStatisticsDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.interfaceStatisticsDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info(String.format("Received interface statistics reply: %s \n", reply.interfaceStatisticsDetails)); + } + } +} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/test/JvppStatsApiTest.java b/java/jvpp-stats/io/fd/jvpp/stats/test/JvppStatsApiTest.java deleted file mode 100644 index 2f9cf9f..0000000 --- a/java/jvpp-stats/io/fd/jvpp/stats/test/JvppStatsApiTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2019 PANTHEON.tech. - * - * 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 io.fd.jvpp.stats.test; - -import io.fd.jvpp.stats.JVppClientStatsImpl; -import io.fd.jvpp.stats.dto.InterfaceStatistics; -import java.util.List; - -class JvppStatsApiTest { - - public static void main(String[] args) { - - JVppClientStatsImpl myImpl = new JVppClientStatsImpl(); - List statistics = myImpl.getInterfaceStatistics(); - - if (statistics == null) { - System.out.println("in java - dump was null"); - System.exit(1); - } - - if (statistics.size() == 0) { - System.out.println("in java - dump was empty"); - System.exit(2); - } - - for (InterfaceStatistics stat: statistics) { - System.out.println(stat.toString()); - } - } -} diff --git a/java/jvpp-stats/io/fd/jvpp/stats/test/Readme.txt b/java/jvpp-stats/io/fd/jvpp/stats/test/Readme.txt index cda9ed2..9fb22cc 100644 --- a/java/jvpp-stats/io/fd/jvpp/stats/test/Readme.txt +++ b/java/jvpp-stats/io/fd/jvpp/stats/test/Readme.txt @@ -2,4 +2,11 @@ This package contains basic tests for jvpp. To run the tests: - Make sure VPP is running - From JVPP's java/ folder execute: - - sudo java -cp jvpp-registry-19.04.jar:jvpp-stats-19.04.jar io.fd.jvpp.stats.test.JvppStatsApiTest + - "sudo java -cp jvpp-registry-19.08.jar:jvpp-stats-19.08.jar io.fd.jvpp.stats.test.FutureApiTest" +- For debug use: + - "sudo java -verbose:jni -Xcheck:jni -cp jvpp-registry-19.08.jar:jvpp-stats-19.08.jar io.fd.jvpp.stats.test.FutureApiTest" + - in the case there is a core dump generated use GDB for backtrace: + - "sudo gdb java core" + - then in GDB console use "bt full" command to print backtrace. + - if libraries are missing use "info sharedlibrary" in GDB to find out path and name of missing libraries. + You can copy missing libraries to desired location (usually /tmp/, because the libs are extracted from jars on startup) \ No newline at end of file diff --git a/java/jvpp-stats/jvpp_interface_stats.h b/java/jvpp-stats/jvpp_interface_stats.h new file mode 100644 index 0000000..01b8cdb --- /dev/null +++ b/java/jvpp-stats/jvpp_interface_stats.h @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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. + */ + +/** + * This file contains JNI bindings for jvpp Java API. + */ +#include +#include + +// JAVA class reference cache +jclass interfaceStatisticsDumpClass; +jclass interfaceStatisticsClass; +jclass interfaceStatisticsDetailsClass; +jclass callbackExceptionClass; + +typedef struct interface_statistics { + int context; + int sw_if_index; + int rx_errors; + int rx_bytes; + int rx_unicast_pkts; + int rx_broadcast_pkts; + int rx_multicast_pkts; + int tx_errors; + int tx_bytes; + int tx_unicast_pkts; + int tx_broadcast_pkts; + int tx_multicast_pkts; + +} vl_api_interface_statistics_details_t; + +static int cache_class_references(JNIEnv *env) { + + interfaceStatisticsDumpClass = (jclass) (*env)->NewGlobalRef(env, (*env)->FindClass(env, + "io/fd/jvpp/stats/dto/InterfaceStatisticsDump")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + interfaceStatisticsDetailsClass = (jclass) (*env)->NewGlobalRef(env, (*env)->FindClass(env, + "io/fd/jvpp/stats/dto/InterfaceStatisticsDetails")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + interfaceStatisticsClass = (jclass) (*env)->NewGlobalRef(env, (*env)->FindClass(env, + "io/fd/jvpp/stats/dto/InterfaceStatistics")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + callbackExceptionClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->FindClass(env, "io/fd/jvpp/VppCallbackException")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + return 0; +} + +static void delete_class_references(JNIEnv *env) { + + if (interfaceStatisticsDumpClass) { + (*env)->DeleteGlobalRef(env, interfaceStatisticsDumpClass); + } + if (interfaceStatisticsDetailsClass) { + (*env)->DeleteGlobalRef(env, interfaceStatisticsDetailsClass); + } + if (interfaceStatisticsClass) { + (*env)->DeleteGlobalRef(env, interfaceStatisticsClass); + } + if (callbackExceptionClass) { + (*env)->DeleteGlobalRef(env, callbackExceptionClass); + } +} + +/** + * Handler for interface_statistics_details message. + */ +static void +interface_statistics_details_handler(JNIEnv *env, vl_api_interface_statistics_details_t *ifc_stats, int ifc_count) { + stats_main_t *plugin_main = &stats_main; + jthrowable exc; + if (CLIB_DEBUG > 1) + clib_warning ("Received interface_statistics_details event message"); + + jmethodID constructor = (*env)->GetMethodID(env, interfaceStatisticsDetailsClass, "", "(II)V"); + + // User does not have to provide callbacks for all VPP messages. + // We are ignoring messages that are not supported by user. + (*env)->ExceptionClear(env); // just in case exception occurred in different place and was not properly cleared + jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "onInterfaceStatisticsDetails", + "(Lio/fd/jvpp/stats/dto/InterfaceStatisticsDetails;)V"); + exc = (*env)->ExceptionOccurred(env); + if (exc) { + clib_warning( + "Unable to extract onInterfaceStatisticsDetails method reference from stats plugin's callbackClass. Ignoring message.\n"); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return; + } + jobject dto = (*env)->NewObject(env, interfaceStatisticsDetailsClass, constructor, ifc_count, ifc_stats->context); + jfieldID interfaceStatisticsId = (*env)->GetFieldID(env, interfaceStatisticsDetailsClass, "interfaceStatistics", + "[Lio/fd/jvpp/stats/dto/InterfaceStatistics;"); + jobject stats_array = (*env)->GetObjectField(env, dto, interfaceStatisticsId); + + jmethodID ifc_stats_constructor = (*env)->GetMethodID(env, interfaceStatisticsClass, "", "(IIIIIIIIIII)V"); + for (int i = 0; i < ifc_count; i++) { + + jobject element = (*env)->NewObject(env, interfaceStatisticsClass, ifc_stats_constructor, + ifc_stats[i].sw_if_index, + ifc_stats[i].tx_errors, + ifc_stats[i].tx_multicast_pkts, + ifc_stats[i].tx_unicast_pkts, + ifc_stats[i].tx_broadcast_pkts, + ifc_stats[i].tx_bytes, + ifc_stats[i].rx_errors, + ifc_stats[i].rx_multicast_pkts, + ifc_stats[i].rx_unicast_pkts, + ifc_stats[i].rx_broadcast_pkts, + ifc_stats[i].rx_bytes); + + (*env)->SetObjectArrayElement(env, stats_array, i, element); + if ((*env)->ExceptionOccurred(env)) { + break; + } + (*env)->DeleteLocalRef(env, element); + } + (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto); + if ((*env)->ExceptionOccurred(env)) { + clib_warning("Unable to call callback for stats plugin's callbackClass.\n"); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return; + } + (*env)->DeleteLocalRef(env, stats_array); + (*env)->DeleteLocalRef(env, dto); +} + +static void +set_field(vl_api_interface_statistics_details_t *stats, int index, const char *field_name, int packets, int bytes) { + if (strcmp(field_name, "/if/rx-error") == 0) { + stats[index].rx_errors = packets; + } else if (strcmp(field_name, "/if/tx-error") == 0) { + stats[index].tx_errors = packets; + } else if (strcmp(field_name, "/if/tx") == 0) { + stats[index].tx_bytes = bytes; + } else if (strcmp(field_name, "/if/tx-multicast") == 0) { + stats[index].tx_multicast_pkts = packets; + } else if (strcmp(field_name, "/if/tx-unicast") == 0) { + stats[index].tx_unicast_pkts = packets; + } else if (strcmp(field_name, "/if/tx-broadcast") == 0) { + stats[index].tx_broadcast_pkts = packets; + } else if (strcmp(field_name, "/if/rx") == 0) { + stats[index].rx_bytes = bytes; + } else if (strcmp(field_name, "/if/rx-multicast") == 0) { + stats[index].rx_multicast_pkts = packets; + } else if (strcmp(field_name, "/if/rx-unicast") == 0) { + stats[index].rx_unicast_pkts = packets; + } else if (strcmp(field_name, "/if/rx-broadcast") == 0) { + stats[index].rx_broadcast_pkts = packets; + } + stats[index].sw_if_index = index; +} + +static stat_segment_data_t *get_ifc_statistics_dump() { + u8 **patterns = 0; + u32 *dir; + vec_add1(patterns, (u8 *) "/if/rx"); + vec_add1(patterns, (u8 *) "/if/tx"); + dir = stat_segment_ls(patterns); + return stat_segment_dump(dir); +} + +static jint getInterfaceStatisticsDump(JNIEnv *env) { + stat_segment_data_t *res; + int i, j, k, interface_count = 0; + u32 my_context_id = vppjni_get_context_id(&jvpp_main); + res = get_ifc_statistics_dump(); + if (res == NULL) { + clib_warning("Interface Statistics dump failed.\n"); + return -1; + } + + if (vec_len (res) > 0) { + if ((res[0].simple_counter_vec != 0) && (vec_len (res[0].simple_counter_vec) > 0)) { + interface_count = vec_len (res[0].simple_counter_vec[0]); + } else if ((res[0].combined_counter_vec != 0) && (vec_len (res[0].combined_counter_vec) > 0)) { + interface_count = vec_len (res[0].combined_counter_vec[0]); + } + } + vl_api_interface_statistics_details_t ifc_stats[interface_count]; + memset(ifc_stats, 0, interface_count * sizeof(vl_api_interface_statistics_details_t)); + ifc_stats->context = my_context_id; + + for (i = 0; i < vec_len (res); i++) { + switch (res[i].type) { + case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE: + if (res[i].simple_counter_vec == 0) + continue; + for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) + for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++) { + set_field(ifc_stats, j, res[i].name, res[i].simple_counter_vec[k][j], 0); + } + break; + + case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: + if (res[i].combined_counter_vec == 0) + continue; + for (k = 0; k < vec_len (res[i].combined_counter_vec); k++) + for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++) { + set_field(ifc_stats, j, res[i].name, res[i].combined_counter_vec[k][j].packets, + res[i].combined_counter_vec[k][j].bytes); + } + break; + + default:; + } + } + stat_segment_data_free(res); + interface_statistics_details_handler(env, ifc_stats, interface_count); + return ifc_stats->context; +} \ No newline at end of file diff --git a/java/jvpp-stats/jvpp_stats.c b/java/jvpp-stats/jvpp_stats.c index 78d69f5..d3a0b18 100644 --- a/java/jvpp-stats/jvpp_stats.c +++ b/java/jvpp-stats/jvpp_stats.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 PANTHEON.tech. + * Copyright (c) 2019 PANTHEON.tech., Cisco and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,123 +14,80 @@ * limitations under the License. */ -#include "io_fd_jvpp_stats_JVppClientStatsImpl.h" +#include "io_fd_jvpp_stats_JVppStatsImpl.h" #include "jvpp_stats.h" -#include +#include "jvpp_interface_stats.h" #include #include +#include +#include +#include +#include +#include +#include +#include +#define vl_typedefs /* define message structures */ -void set_field(int_stats_t* stats, int index, const char* field_name, int packets, int bytes) { - if (strcmp(field_name, "/if/rx-error") == 0) { - stats[index].rx_errors = packets; - } else if (strcmp(field_name, "/if/tx-error") == 0) { - stats[index].tx_errors = packets; - } else if (strcmp(field_name, "/if/tx") == 0) { - stats[index].tx_bytes = bytes; - } else if (strcmp(field_name, "/if/tx-multicast") == 0) { - stats[index].tx_multicast_pkts= packets; - } else if (strcmp(field_name, "/if/tx-unicast") == 0) { - stats[index].tx_unicast_pkts= packets; - } else if (strcmp(field_name, "/if/tx-broadcast") == 0) { - stats[index].tx_broadcast_pkts= packets; - } else if (strcmp(field_name, "/if/rx") == 0) { - stats[index].rx_bytes = bytes; - } else if (strcmp(field_name, "/if/rx-multicast") == 0) { - stats[index].rx_multicast_pkts= packets; - } else if (strcmp(field_name, "/if/rx-unicast") == 0) { - stats[index].rx_unicast_pkts= packets; - } else if (strcmp(field_name, "/if/rx-broadcast") == 0) { - stats[index].rx_broadcast_pkts= packets; - } -} -stat_segment_data_t* get_statistics_dump() { - u8 **patterns = 0; - u32 *dir; - vec_add1(patterns, (u8*)"/if/rx"); - vec_add1(patterns, (u8*)"/if/tx"); - dir = stat_segment_ls(patterns); - return stat_segment_dump(dir); -} +#include -JNIEXPORT jobjectArray JNICALL Java_io_fd_jvpp_stats_JVppClientStatsImpl_interfaceStatisticsDump(JNIEnv *env, - jclass jclazz) { +#undef vl_typedefs - stat_segment_data_t *res; - int i, j, k, interface_count; +JNIEXPORT void JNICALL Java_io_fd_jvpp_stats_JVppStatsImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + stats_main_t *plugin_main = &stats_main; + clib_warning ("Java_io_fd_jvpp_stats_JVppStatsImpl_init0"); - res = get_statistics_dump(); - if (res == NULL) { - fprintf (stderr, "Interface Statistics dump failed.\n"); - return NULL; - } + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, svm_queue_t *); - if (vec_len (res) > 0) { - if ((res[0].simple_counter_vec != 0) && (vec_len (res[0].simple_counter_vec) > 0)) { - interface_count = vec_len (res[0].simple_counter_vec[0]); - } else if ((res[0].combined_counter_vec != 0) && (vec_len (res[0].combined_counter_vec) > 0)) { - interface_count = vec_len (res[0].combined_counter_vec[0]); - } - } - int_stats_t ifc_stats[interface_count]; - memset(ifc_stats, 0, interface_count*sizeof(int_stats_t)); + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass) (*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); - for (i = 0; i < vec_len (res); i++) - { - switch (res[i].type) - { - case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE: - if (res[i].simple_counter_vec == 0) - continue; - for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) - for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++) { - set_field(ifc_stats, j, res[i].name, res[i].simple_counter_vec[k][j], 0); - } - break; + int rv = stat_segment_connect(STAT_SEGMENT_SOCKET_FILE); + if (rv < 0) { + clib_warning("Couldn't connect to %s. Check if socket exists/permissions.(ret stat: %d)\n", + STAT_SEGMENT_SOCKET_FILE, rv); + return; + } +} - case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: - if (res[i].combined_counter_vec == 0) - continue; - for (k = 0; k < vec_len (res[i].combined_counter_vec); k++) - for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++) { - set_field(ifc_stats, j, res[i].name, res[i].combined_counter_vec[k][j].packets, - res[i].combined_counter_vec[k][j].bytes); - } - break; +JNIEXPORT void JNICALL Java_io_fd_jvpp_stats_JVppStatsImpl_close0 + (JNIEnv *env, jclass clazz) { + stats_main_t *plugin_main = &stats_main; + stat_segment_disconnect(); + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); - default: - ; - } - } - stat_segment_data_free (res); + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} - jobjectArray retArray; - jclass ifcStatsClass = (*env)->FindClass(env, "io/fd/jvpp/stats/dto/InterfaceStatistics"); - retArray= (jobjectArray)(*env)->NewObjectArray(env, interface_count,ifcStatsClass, NULL); - jmethodID constructor = (*env)->GetMethodID(env, ifcStatsClass, "", "(IIIIIIIIIII)V"); +JNIEXPORT jint JNICALL Java_io_fd_jvpp_stats_JVppStatsImpl_interfaceStatisticsDump0(JNIEnv *env, jclass jclazz) { + return getInterfaceStatisticsDump(env); +} - for (int i = 0; i < interface_count; i++) { - jobject newClientObj = (*env)->NewObject(env, ifcStatsClass, constructor, i, - ifc_stats[i].tx_errors, ifc_stats[i].tx_multicast_pkts, ifc_stats[i].tx_unicast_pkts, - ifc_stats[i].tx_broadcast_pkts, ifc_stats[i].tx_bytes, ifc_stats[i].rx_errors, ifc_stats[i].rx_multicast_pkts, - ifc_stats[i].rx_unicast_pkts, ifc_stats[i].rx_broadcast_pkts, ifc_stats[i].rx_bytes); - (*env)->SetObjectArrayElement(env,retArray,i,newClientObj); +/* Attach thread to JVM and cache class references when initiating JVPP Stats */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env; + if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; } - return retArray; -} -JNIEXPORT jint JNICALL Java_io_fd_jvpp_stats_JVppClientStatsImpl_statSegmentConnect(JNIEnv *env, jclass jclazz) { - u8 * stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE; - int rv = stat_segment_connect((char*)stat_segment_name); - if (rv < 0) { - fprintf(stderr, "Couldn't connect to %s. Check if socket exists/permissions.(ret stat: %d)\n", - stat_segment_name, rv); - return -1; + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; } - return 0; -} -JNIEXPORT void JNICALL Java_io_fd_jvpp_stats_JVppClientStatsImpl_statSegmentDisconnect(JNIEnv *env, jclass jclazz) { - stat_segment_disconnect(); - return; + return JNI_VERSION_1_8; } + +/* Clean up cached references when disposing JVPP Stats */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv *env; + if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} \ No newline at end of file diff --git a/java/jvpp-stats/jvpp_stats.h b/java/jvpp-stats/jvpp_stats.h index 7014101..d38414b 100644 --- a/java/jvpp-stats/jvpp_stats.h +++ b/java/jvpp-stats/jvpp_stats.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 PANTHEON.tech. + * Copyright (c) 2019 PANTHEON.tech., Cisco and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,20 +17,32 @@ #ifndef PROJECT_JVPP_STATS_H #define PROJECT_JVPP_STATS_H -#endif //PROJECT_JVPP_STATS_H +#include +#include +#include +#include +#include +#include + +/* Global state for JVPP-STATS */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t *vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; +} stats_main_t; +stats_main_t stats_main __attribute__((aligned (64))); -typedef struct interface_statistics{ - int rx_errors; - int rx_bytes; - int rx_unicast_pkts; - int rx_broadcast_pkts; - int rx_multicast_pkts; - int tx_errors; - int tx_bytes; - int tx_unicast_pkts; - int tx_broadcast_pkts; - int tx_multicast_pkts; -} int_stats_t; - -int_stats_t interface_statistics __attribute__((aligned (64))); +JNIEXPORT void JNICALL Java_io_fd_jvpp_stats_JVppStatsImpl_init0(JNIEnv *, jclass, jobject, jlong, jint); + +JNIEXPORT void JNICALL Java_io_fd_jvpp_stats_JVppStatsImpl_close0(JNIEnv *, jclass); + +JNIEXPORT jint JNICALL Java_io_fd_jvpp_stats_JVppStatsImpl_interfaceStatisticsDump0(JNIEnv *, jclass); + +#endif //PROJECT_JVPP_STATS_H diff --git a/java/jvpp-stats/jvpp_stats_registry.c b/java/jvpp-stats/jvpp_stats_registry.c new file mode 100644 index 0000000..3b2db38 --- /dev/null +++ b/java/jvpp-stats/jvpp_stats_registry.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2019 Cisco and/or its affiliates. + * + * 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. + */ + +#include + +#include +#include "io_fd_jvpp_stats_VppStatsJNIConnection.h" +#include "io_fd_jvpp_stats_JVppStatsRegistryImpl.h" +#include + +/* + * The Java runtime isn't compile w/ -fstack-protector, + * so we have to supply missing external references for the + * regular vpp libraries. + */ +void __stack_chk_guard(void) __attribute__((weak)); +void __stack_chk_guard(void) { +} + +typedef struct { + jobject registryObject; + jclass registryClass; + jclass callbackExceptionClass; + + /* Connected indication */ + volatile u8 is_connected; + u32 vpe_pid; +} jvpp_stats_registry_main_t; + +jvpp_stats_registry_main_t jvpp_stats_registry_main __attribute__((aligned (64))); + +// JAVA class reference cache +jclass connectionInfoClass; +jclass callbackExceptionClass; + +JNIEXPORT jint JNICALL Java_io_fd_jvpp_stats_JVppStatsRegistryImpl_controlPing0(JNIEnv *env, jobject registryObject) { + u32 my_context_id = vppjni_get_context_id(&jvpp_main); + jvpp_stats_registry_main_t *rm = &jvpp_stats_registry_main; + + if (rm->registryObject == 0) { + rm->registryObject = (*env)->NewGlobalRef(env, registryObject); + } + if (rm->registryClass == 0) { + rm->registryClass = (jclass) (*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, registryObject)); + } + + rm->is_connected = 1; + return my_context_id; +} + +JNIEXPORT jobject JNICALL +Java_io_fd_jvpp_stats_VppStatsJNIConnection_statsConnect(JNIEnv *env, jclass obj, jstring clientName) { + // int rv; + const char *client_name; + jvpp_main_t * jm = &jvpp_main; + jobject con_info; + jthrowable exc; + jvpp_stats_registry_main_t * rm = &jvpp_stats_registry_main; + + jmethodID connectionInfoConstructor = (*env)->GetMethodID(env, connectionInfoClass, "", "(JIII)V"); + exc = (*env)->ExceptionOccurred(env); + if (exc) { + clib_warning( + "Unable to extract connectionInfoClass constructor. Ignoring message.\n"); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + + con_info = (*env)->NewObject(env, connectionInfoClass, connectionInfoConstructor, 0, 0, + VNET_API_ERROR_INVALID_REGISTRATION, 0); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + clib_warning("StatsConnectionInfo: Invalid Registration!\n"); + } + return con_info; + } + + if (rm->is_connected) { + con_info = (*env)->NewObject(env, connectionInfoClass, connectionInfoConstructor, 0, 0, + VNET_API_ERROR_ALREADY_CONNECTED, 0); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + clib_warning("StatsConnectionInfo: Already connected.\n"); + } + return con_info; + } + + client_name = (*env)->GetStringUTFChars(env, clientName, 0); + + if (!client_name) { + con_info = (*env)->NewObject(env, connectionInfoClass, connectionInfoConstructor, 0, 0, + VNET_API_ERROR_INVALID_VALUE, 0); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + clib_warning("StatsConnectionInfo: Invalid Client name!\n"); + } + return con_info; + } + + (*env)->ReleaseStringUTFChars(env, clientName, client_name); + + con_info = (*env)->NewObject(env, connectionInfoClass, connectionInfoConstructor, + (jlong) pointer_to_uword(jm->vl_input_queue), (jint) jm->my_client_index, (jint) 0, + (jint) rm->vpe_pid); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + clib_warning("StatsConnectionInfo: Unspecified Error! Exiting.\n"); + return NULL; + } + + return con_info; +} + + +JNIEXPORT void JNICALL Java_io_fd_jvpp_stats_VppStatsJNIConnection_statsDisconnect( + JNIEnv *env, jclass clazz) { + jvpp_stats_registry_main_t * rm = &jvpp_stats_registry_main; + rm->is_connected = 0; // TODO make thread safe + // cleanup: + if (rm->registryObject) { + (*env)->DeleteGlobalRef(env, rm->registryObject); + rm->registryObject = 0; + } + if (rm->registryClass) { + (*env)->DeleteGlobalRef(env, rm->registryClass); + rm->registryClass = 0; + } +} + +static int cache_class_references(JNIEnv *env) { + connectionInfoClass = + (jclass) (*env)->NewGlobalRef(env, (*env)->FindClass(env, + "io/fd/jvpp/stats/VppStatsJNIConnection$StatsConnectionInfo")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + + callbackExceptionClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->FindClass(env, "io/fd/jvpp/VppCallbackException")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + return 0; +} + +static void delete_class_references(JNIEnv *env) { + + if (connectionInfoClass) { + (*env)->DeleteGlobalRef(env, connectionInfoClass); + } + if (callbackExceptionClass) { + (*env)->DeleteGlobalRef(env, callbackExceptionClass); + } +} + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + jvpp_main_t * jm = &jvpp_main; + jvpp_stats_registry_main_t * rm = &jvpp_stats_registry_main; + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + rm->callbackExceptionClass = connectionInfoClass; + jm->jvm = vm; + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) { + jvpp_main_t * jm = &jvpp_main; + jvpp_stats_registry_main_t * rm = &jvpp_stats_registry_main; + + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + + delete_class_references(env); + rm->callbackExceptionClass = NULL; + + jm->jenv = NULL; + jm->jvm = NULL; +} -- cgit 1.2.3-korg