Skip to content

Commit

Permalink
eBPF and extended perf_events support
Browse files Browse the repository at this point in the history
  • Loading branch information
apangin committed Jul 25, 2021
1 parent af1cf93 commit 0f0ef33
Show file tree
Hide file tree
Showing 29 changed files with 2,061 additions and 50 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ It features OS capabilities and JDK internal APIs essential for making your high

one-nio highlights
==================
- Own native socket library.
- APIs for managing gigabytes of RAM beyond Java Heap.
- Optimized native socket library.
- API for managing gigabytes of RAM beyond Java Heap.
- Fast and compact serialization mechanism.
- RPC client/server technology which is an order of magnitude faster than Java RMI.
- Java accessors for perf events and eBFP.

More info on the wiki page
==========================
Expand Down
15 changes: 13 additions & 2 deletions src/one/nio/net/native/jni_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ void throw_channel_closed(JNIEnv* env) {
throw_by_name(env, "java/nio/channels/ClosedChannelException", NULL);
}

void throw_io_exception(JNIEnv* env) {
int error_code = errno;
void throw_illegal_argument(JNIEnv* env) {
throw_by_name(env, "java/lang/IllegalArgumentException", NULL);
}

void throw_illegal_argument_msg(JNIEnv* env, const char* msg) {
throw_by_name(env, "java/lang/IllegalArgumentException", msg);
}

void throw_io_exception_code(JNIEnv* env, int error_code) {
switch (error_code) {
case ETIMEDOUT:
case EINPROGRESS:
Expand Down Expand Up @@ -83,3 +90,7 @@ void throw_io_exception(JNIEnv* env) {
break;
}
}

void throw_io_exception(JNIEnv* env) {
throw_io_exception_code(env, errno);
}
3 changes: 3 additions & 0 deletions src/one/nio/net/native/jni_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ int array_equals(JNIEnv* env, jbyteArray array, void* buf, int buflen);
void throw_by_name(JNIEnv* env, const char* exception, const char* msg);
void throw_socket_closed(JNIEnv* env);
void throw_channel_closed(JNIEnv* env);
void throw_illegal_argument(JNIEnv* env);
void throw_illegal_argument_msg(JNIEnv* env, const char* msg);
void throw_io_exception(JNIEnv* env);
void throw_io_exception_code(JNIEnv* env, int error_code);

static inline int is_io_exception(int fd) {
if (errno == EINTR) {
Expand Down
56 changes: 56 additions & 0 deletions src/one/nio/os/Cpus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2021 Odnoklassniki Ltd, Mail.Ru Group
*
* 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 one.nio.os;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import one.nio.util.Utf8;

public class Cpus {
private static final Log log = LogFactory.getLog(Cpus.class);

public static final int ONLINE = cpus("/sys/devices/system/cpu/online");
public static final int POSSIBLE = cpus("/sys/devices/system/cpu/possible");
public static final int PRESENT = cpus("/sys/devices/system/cpu/present");

private static int cpus(String rangeFile) {
try {
byte[] bytes = Files.readAllBytes(Paths.get(rangeFile));
String rangeStr = Utf8.read(bytes, 0, bytes.length).trim();
int cpus = 0;
for (String range : rangeStr.split(",")) {
String[] s = range.split("-");
if (s.length == 1) {
cpus++;
} else {
cpus += 1 + Integer.parseInt(s[1]) - Integer.parseInt(s[0]);
}
}
return cpus;
} catch (IOException e) {
if (log.isDebugEnabled()) {
log.debug("Failed to read " + rangeFile, e);
}
return Runtime.getRuntime().availableProcessors();
}
}
}
65 changes: 65 additions & 0 deletions src/one/nio/os/bpf/Bpf.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2021 Odnoklassniki Ltd, Mail.Ru Group
*
* 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 one.nio.os.bpf;

import one.nio.os.NativeLibrary;

import java.io.IOException;
import java.lang.annotation.Native;

public class Bpf {
public static final boolean IS_SUPPORTED = NativeLibrary.IS_SUPPORTED;

static final int OBJ_PROG = 0;
static final int OBJ_MAP = 1;

static native int objGetNextId(int type, int startId);

static native int progLoad(String path, int type) throws IOException;

static native int objectGet(String pathName) throws IOException;

static native void objectPin(int fd, String pathName) throws IOException;

static native int progGetFdById(int id) throws IOException;

static native int mapGetFdById(int id) throws IOException;

static native String progGetInfo(int fd, int[] result) throws IOException;

static native int[] progGetMapIds(int fd) throws IOException;

static native int rawTracepointOpen(int progFd, String name) throws IOException;

static native String mapGetInfo(int fd, int[] result /*type,id,key_size,value_size,max_entries,flags*/) throws IOException;

static native int mapCreate(int type, int keySize, int valueSize, int maxEntries, String name, int flags) throws IOException;

/* flags for lookup/update */
@Native static final int BPF_ANY = 0; // create new element or update existing
@Native static final int BPF_NOEXIST = 1; // create new element if it didn't exist
@Native static final int BPF_EXIST = 2; // update existing element
@Native static final int BPF_F_LOCK = 4; // spin_lock-ed map_lookup/map_update

static native boolean mapLookup(int fd, byte[] key, byte[] result, int flags) throws IOException;

static native boolean mapUpdate(int fd, byte[] key, byte[] value, int flags) throws IOException;

static native boolean mapRemove(int fd, byte[] key) throws IOException;

static native boolean mapGetNextKey(int fd, byte[] key, byte[] nextKey);
}
205 changes: 205 additions & 0 deletions src/one/nio/os/bpf/BpfMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* Copyright 2021 Odnoklassniki Ltd, Mail.Ru Group
*
* 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 one.nio.os.bpf;

import one.nio.os.Cpus;

import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class BpfMap extends BpfObj implements Closeable {
public static final int CPUS = Cpus.POSSIBLE;
public static final int ARRAY_KEY_SIZE = 4;

public final MapType type;
public final int keySize;
public final int valueSize;
public final int totalValueSize;
public final int maxEntries;
public final int flags;

BpfMap(MapType type, int id, String name, int keySize, int valueSize, int maxEntries, int flags, int fd) {
super(id, name, fd);
this.keySize = keySize;
this.valueSize = valueSize;
this.maxEntries = maxEntries;
this.flags = flags;
// see kernel/bpf/syscall.c:bpf_map_value_size
this.totalValueSize = type.perCpu ? roundUp(valueSize) * CPUS : valueSize;
this.type = type;
}

private static int roundUp(int valueSize) {
return (valueSize + 7) & ~7;
}

public boolean get(byte[] key, byte[] result) throws IOException {
return get(key, result, 0);
}

protected boolean get(byte[] key, byte[] result, int flags) throws IOException {
checkKeyLength(key.length);
checkTotalValueLength(result.length);
return Bpf.mapLookup(fd(), key, result, flags);
}

public byte[] get(byte[] key) throws IOException {
checkKeyLength(key.length);
byte[] result = new byte[totalValueSize];
boolean res = get(key, result);
return res ? result : null;
}

public boolean put(byte[] key, byte[] value) throws IOException {
return put(key, value, Bpf.BPF_ANY);
}

public boolean putIfAbsent(byte[] key, byte[] value) throws IOException {
return put(key, value, Bpf.BPF_NOEXIST);
}

public boolean putIfPresent(byte[] key, byte[] value) throws IOException {
return put(key, value, Bpf.BPF_EXIST);
}

protected boolean put(byte[] key, byte[] value, int flags) throws IOException {
checkKeyLength(key.length);
checkTotalValueLength(value.length);
return Bpf.mapUpdate(fd(), key, value, flags);
}

public boolean remove(byte[] key) throws IOException {
checkKeyLength(key.length);
return Bpf.mapRemove(fd(), key);
}

public Iterable<byte[]> keys() {
return KeysIterator::new;
}

public BpfMap synchronizedMap() {
return new SynchronizedBpfMap(this);
}

public static BpfMap getPinned(String path) throws IOException {
int fd = Bpf.objectGet(path);
return getByFd(fd);
}

public static BpfMap getById(int id) throws IOException {
int fd = Bpf.mapGetFdById(id);
return getByFd(fd);
}

public static BpfMap getByFd(int fd) throws IOException {
int[] res = new int[6];
String name = Bpf.mapGetInfo(fd, res);
MapType type = MapType.values()[res[0]];
int id = res[1];
int keySize = res[2];
int valueSize = res[3];
int maxEntries = res[4];
int flags = res[5];
return new BpfMap(type, id, name, keySize, valueSize, maxEntries, flags, fd);
}

public static BpfMap newMap(MapType type, int keySize, int valueSize, int maxEntries, String name, int flags) throws IOException {
int fd = Bpf.mapCreate(type.ordinal(), keySize, valueSize, maxEntries, name, flags);
return getByFd(fd);
}

public static BpfMap newPerfEventArray(String name, int flags) throws IOException {
return newMap(MapType.PERF_EVENT_ARRAY, ARRAY_KEY_SIZE, 4, CPUS, name, flags);
}

private void checkKeyLength(int length) {
if (keySize != length) {
throw new IllegalArgumentException("Invalid key size");
}
}

private void checkTotalValueLength(int length) {
if (totalValueSize != length) {
throw new IllegalArgumentException("Invalid value size");
}
}

public static Iterable<Integer> getAllIds() {
return () -> new IdsIterator(Bpf.OBJ_MAP);
}

public static byte[] bytes(int i) {
return ByteBuffer.allocate(4).order(ByteOrder.nativeOrder()).putInt(i).array();
}

public static byte[] bytes(long i) {
return ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(i).array();
}

public static IntBuffer ints(byte[] value) {
return ByteBuffer.wrap(value).order(ByteOrder.nativeOrder()).asIntBuffer();
}

public static LongBuffer longs(byte[] value) {
return ByteBuffer.wrap(value).order(ByteOrder.nativeOrder()).asLongBuffer();
}

private static class SynchronizedBpfMap extends BpfMap {
public SynchronizedBpfMap(BpfMap map) {
super(map.type, map.id, map.name, map.keySize, map.valueSize, map.maxEntries, map.flags, map.fd());
}

@Override
protected boolean get(byte[] key, byte[] result, int flags) throws IOException {
return super.get(key, result, flags | Bpf.BPF_F_LOCK);
}

@Override
protected boolean put(byte[] key, byte[] value, int flags) throws IOException {
return super.put(key, value, flags | Bpf.BPF_F_LOCK);
}
}

class KeysIterator implements Iterator<byte[]> {
byte[] next;
boolean nextChecked;

@Override
public boolean hasNext() {
if (!nextChecked) {
byte[] buf = new byte[keySize];
boolean hasNext = Bpf.mapGetNextKey(fd(), next, buf);
next = hasNext ? buf : null;
nextChecked = true;
}
return next != null;
}

@Override
public byte[] next() {
if (!hasNext()) throw new NoSuchElementException();
nextChecked = false;
return next;
}
}
}
Loading

0 comments on commit 0f0ef33

Please sign in to comment.