From 0f0ef3396cd152a3abc669589177aa2a5bbe3f6e Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Mon, 26 Jul 2021 00:08:36 +0300 Subject: [PATCH] eBPF and extended perf_events support --- README.md | 5 +- src/one/nio/net/native/jni_util.c | 15 +- src/one/nio/net/native/jni_util.h | 3 + src/one/nio/os/Cpus.java | 56 +++ src/one/nio/os/bpf/Bpf.java | 65 +++ src/one/nio/os/bpf/BpfMap.java | 205 ++++++++ src/one/nio/os/bpf/BpfObj.java | 64 +++ src/one/nio/os/bpf/BpfProg.java | 75 +++ src/one/nio/os/bpf/Handle.java | 51 ++ src/one/nio/os/bpf/MapType.java | 48 ++ src/one/nio/os/bpf/ProgType.java | 44 ++ src/one/nio/os/native/bpf.c | 445 ++++++++++++++++++ src/one/nio/os/native/perf.c | 26 +- src/one/nio/os/perf/CounterValue.java | 27 ++ src/one/nio/os/perf/GlobalValue.java | 70 +++ src/one/nio/os/perf/LocalValue.java | 53 +++ src/one/nio/os/perf/Perf.java | 69 +-- src/one/nio/os/perf/PerfCounter.java | 44 +- src/one/nio/os/perf/PerfCounterGlobal.java | 55 ++- src/one/nio/os/perf/PerfEvent.java | 12 +- src/one/nio/os/perf/PerfOption.java | 9 +- .../nio/os/perf/PerfOptionGlobalGroup.java | 26 + test/one/nio/os/bpf/BpfListTest.java | 34 ++ test/one/nio/os/bpf/BpfMapDumpTest.java | 60 +++ test/one/nio/os/bpf/BpfMapTest.java | 104 ++++ test/one/nio/os/bpf/BpfProgTest.java | 48 ++ test/one/nio/os/perf/BrokenCounterTest.java | 38 ++ test/one/nio/os/perf/PerfStat.java | 289 ++++++++++++ test/one/nio/os/perf/RawAMDL3MissLatency.java | 71 +++ 29 files changed, 2061 insertions(+), 50 deletions(-) create mode 100644 src/one/nio/os/Cpus.java create mode 100644 src/one/nio/os/bpf/Bpf.java create mode 100644 src/one/nio/os/bpf/BpfMap.java create mode 100644 src/one/nio/os/bpf/BpfObj.java create mode 100644 src/one/nio/os/bpf/BpfProg.java create mode 100644 src/one/nio/os/bpf/Handle.java create mode 100644 src/one/nio/os/bpf/MapType.java create mode 100644 src/one/nio/os/bpf/ProgType.java create mode 100644 src/one/nio/os/native/bpf.c create mode 100644 src/one/nio/os/perf/CounterValue.java create mode 100644 src/one/nio/os/perf/GlobalValue.java create mode 100644 src/one/nio/os/perf/LocalValue.java create mode 100644 src/one/nio/os/perf/PerfOptionGlobalGroup.java create mode 100644 test/one/nio/os/bpf/BpfListTest.java create mode 100644 test/one/nio/os/bpf/BpfMapDumpTest.java create mode 100644 test/one/nio/os/bpf/BpfMapTest.java create mode 100644 test/one/nio/os/bpf/BpfProgTest.java create mode 100644 test/one/nio/os/perf/BrokenCounterTest.java create mode 100644 test/one/nio/os/perf/PerfStat.java create mode 100644 test/one/nio/os/perf/RawAMDL3MissLatency.java diff --git a/README.md b/README.md index 79119e2..2ae9bfb 100644 --- a/README.md +++ b/README.md @@ -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 ========================== diff --git a/src/one/nio/net/native/jni_util.c b/src/one/nio/net/native/jni_util.c index d470c95..a297fca 100755 --- a/src/one/nio/net/native/jni_util.c +++ b/src/one/nio/net/native/jni_util.c @@ -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: @@ -83,3 +90,7 @@ void throw_io_exception(JNIEnv* env) { break; } } + +void throw_io_exception(JNIEnv* env) { + throw_io_exception_code(env, errno); +} diff --git a/src/one/nio/net/native/jni_util.h b/src/one/nio/net/native/jni_util.h index 389cde1..aed15da 100755 --- a/src/one/nio/net/native/jni_util.h +++ b/src/one/nio/net/native/jni_util.h @@ -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) { diff --git a/src/one/nio/os/Cpus.java b/src/one/nio/os/Cpus.java new file mode 100644 index 0000000..19c9e72 --- /dev/null +++ b/src/one/nio/os/Cpus.java @@ -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(); + } + } +} diff --git a/src/one/nio/os/bpf/Bpf.java b/src/one/nio/os/bpf/Bpf.java new file mode 100644 index 0000000..f1c69d4 --- /dev/null +++ b/src/one/nio/os/bpf/Bpf.java @@ -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); +} diff --git a/src/one/nio/os/bpf/BpfMap.java b/src/one/nio/os/bpf/BpfMap.java new file mode 100644 index 0000000..a5c7ef8 --- /dev/null +++ b/src/one/nio/os/bpf/BpfMap.java @@ -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 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 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[] 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; + } + } +} diff --git a/src/one/nio/os/bpf/BpfObj.java b/src/one/nio/os/bpf/BpfObj.java new file mode 100644 index 0000000..970d1e7 --- /dev/null +++ b/src/one/nio/os/bpf/BpfObj.java @@ -0,0 +1,64 @@ +/* + * 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 java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public abstract class BpfObj extends Handle { + public static final int MAX_NAME_LEN = 15; + + public final int id; + public final String name; + + public BpfObj(int id, String name, int fd) { + super(fd); + this.id = id; + this.name = name; + } + + public void pin(String path) throws IOException { + Bpf.objectPin(fd(), path); + } + + static class IdsIterator implements Iterator { + final int type; + int next; + boolean nextChecked; + + IdsIterator(int type) { + this.type = type; + } + + @Override + public boolean hasNext() { + if (!nextChecked) { + next = Bpf.objGetNextId(type, next); + nextChecked = true; + } + return next > 0; + } + + @Override + public Integer next() { + if (!hasNext()) throw new NoSuchElementException(); + nextChecked = false; + return next; + } + } +} diff --git a/src/one/nio/os/bpf/BpfProg.java b/src/one/nio/os/bpf/BpfProg.java new file mode 100644 index 0000000..69b8189 --- /dev/null +++ b/src/one/nio/os/bpf/BpfProg.java @@ -0,0 +1,75 @@ +/* + * 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.perf.PerfCounter; + +import java.io.IOException; + +public class BpfProg extends BpfObj { + public final ProgType type; + + BpfProg(ProgType type, int id, String name, int fd) { + super(id, name, fd); + this.type = type; + } + + public static BpfProg load(String path, ProgType type) throws IOException { + int fd = Bpf.progLoad(path, type.ordinal()); + return getByFd(fd); + } + + public static BpfProg getPinned(String path) throws IOException { + int fd = Bpf.objectGet(path); + return getByFd(fd); + } + + public static BpfProg getById(int id) throws IOException { + int fd = Bpf.progGetFdById(id); + return getByFd(fd); + } + + public static BpfProg getByFd(int fd) throws IOException { + int[] result = new int[2]; + String name = Bpf.progGetInfo(fd, result); + ProgType type = ProgType.values()[result[0]]; + int id = result[1]; + return new BpfProg(type, id, name, fd); + } + + public void attach(PerfCounter counter) throws IOException { + if (type != ProgType.PERF_EVENT) { + throw new IllegalStateException(); + } + counter.attachBpf(fd()); + } + + public Handle attachRawTracepoint(String name) throws IOException { + if (type != ProgType.RAW_TRACEPOINT) { + throw new IllegalStateException(); + } + return new Handle(Bpf.rawTracepointOpen(fd(), name)); + } + + public int[] getMapIds() throws IOException { + return Bpf.progGetMapIds(fd()); + } + + public static Iterable getAllIds() { + return () -> new IdsIterator(Bpf.OBJ_PROG); + } +} diff --git a/src/one/nio/os/bpf/Handle.java b/src/one/nio/os/bpf/Handle.java new file mode 100644 index 0000000..788bdee --- /dev/null +++ b/src/one/nio/os/bpf/Handle.java @@ -0,0 +1,51 @@ +/* + * 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.perf.Perf; + +import java.io.Closeable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +public class Handle implements Closeable { + private volatile int fd; + + static final AtomicIntegerFieldUpdater fdUpdater = + AtomicIntegerFieldUpdater.newUpdater(Handle.class, "fd"); + + public Handle(int fd) { + if (fd <= 0) { + throw new IllegalArgumentException("Invalid " + getClass().getSimpleName() + " fd: " + fd); + } + this.fd = fd; + } + + protected int fd() { + if (fd <= 0) { + throw new IllegalStateException(getClass().getSimpleName() + " is closed"); + } + return fd; + } + + @Override + public void close() { + int fd = this.fd; + if (fdUpdater.compareAndSet(this, fd, -1)) { + Perf.close(fd); + } + } +} diff --git a/src/one/nio/os/bpf/MapType.java b/src/one/nio/os/bpf/MapType.java new file mode 100644 index 0000000..4439a17 --- /dev/null +++ b/src/one/nio/os/bpf/MapType.java @@ -0,0 +1,48 @@ +/* + * 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; + +// see bpf.h +public enum MapType { + UNSPEC, + HASH, + ARRAY, + PROG_ARRAY, + PERF_EVENT_ARRAY, + PERCPU_HASH, + PERCPU_ARRAY, + STACK_TRACE, + CGROUP_ARRAY, + LRU_HASH, + LRU_PERCPU_HASH, + LPM_TRIE, + ARRAY_OF_MAPS, + HASH_OF_MAPS, + DEVMAP, + SOCKMAP, + CPUMAP, + XSKMAP, + SOCKHASH, + CGROUP_STORAGE, + REUSEPORT_SOCKARRAY, + PERCPU_CGROUP_STORAGE, + QUEUE, + STACK, + ; + + final boolean perCpu = name().contains("PERCPU_"); +} diff --git a/src/one/nio/os/bpf/ProgType.java b/src/one/nio/os/bpf/ProgType.java new file mode 100644 index 0000000..ef3691a --- /dev/null +++ b/src/one/nio/os/bpf/ProgType.java @@ -0,0 +1,44 @@ +/* + * 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; + +// see bpf.h +public enum ProgType { + UNSPEC, + SOCKET_FILTER, + KPROBE, + SCHED_CLS, + SCHED_ACT, + TRACEPOINT, + XDP, + PERF_EVENT, + CGROUP_SKB, + CGROUP_SOCK, + LWT_IN, + LWT_OUT, + LWT_XMIT, + SOCK_OPS, + SK_SKB, + CGROUP_DEVICE, + SK_MSG, + RAW_TRACEPOINT, + CGROUP_SOCK_ADDR, + LWT_SEG6LOCAL, + LIRC_MODE2, + SK_REUSEPORT, + FLOW_DISSECTOR, +} diff --git a/src/one/nio/os/native/bpf.c b/src/one/nio/os/native/bpf.c new file mode 100644 index 0000000..499c5f2 --- /dev/null +++ b/src/one/nio/os/native/bpf.c @@ -0,0 +1,445 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../net/native/jni_util.h" + + +static inline __u64 ptr_to_u64(const void* ptr) { + return (__u64)(uintptr_t)ptr; +} + +static inline int sys_bpf(enum bpf_cmd cmd, union bpf_attr* attr, unsigned int size) { + return syscall(__NR_bpf, cmd, attr, size); +} + +struct bpf_object; + +int (*bpf_prog_load)(const char *file, enum bpf_prog_type type, + struct bpf_object **pobj, int *prog_fd); + + +JNIEXPORT jint JNICALL +Java_one_nio_os_bpf_Bpf_progLoad(JNIEnv* env, jclass cls, jstring pathname, jint type) { + if (bpf_prog_load == NULL) { + void* libbpf = dlopen("libbpf.so", RTLD_LAZY | RTLD_GLOBAL); + if (libbpf == NULL) { + throw_by_name(env, "java/lang/UnsupportedOperationException", "Failed to load libbpf.so"); + return -EINVAL; + } + bpf_prog_load = dlsym(libbpf, "bpf_prog_load"); + if (bpf_prog_load == NULL) { + dlclose(libbpf); + throw_by_name(env, "java/lang/UnsupportedOperationException", "libbpf.so bpf_prog_load method not found"); + return -EINVAL; + } + } + + if (pathname == NULL) { + throw_illegal_argument(env); + return -EINVAL; + } + + const char* c_pathname = (*env)->GetStringUTFChars(env, pathname, NULL); + int fd = 0; + struct bpf_object* pobj; + + int res = bpf_prog_load(c_pathname, type, &pobj, &fd); + if (res < 0) { + throw_io_exception_code(env, -res); + } + + (*env)->ReleaseStringUTFChars(env, pathname, c_pathname); + + return fd; +} + +JNIEXPORT jint JNICALL +Java_one_nio_os_bpf_Bpf_objectGet(JNIEnv* env, jclass cls, jstring pathname) { + if (pathname == NULL) { + throw_illegal_argument(env); + return -EINVAL; + } + + const char* c_pathname = (*env)->GetStringUTFChars(env, pathname, NULL); + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.pathname = ptr_to_u64(c_pathname); + + int fd = sys_bpf(BPF_OBJ_GET, &attr, sizeof(attr)); + if (fd < 0) { + throw_io_exception(env); + } + + (*env)->ReleaseStringUTFChars(env, pathname, c_pathname); + + return fd; +} + +JNIEXPORT void JNICALL +Java_one_nio_os_bpf_Bpf_objectPin(JNIEnv* env, jclass cls, int fd, jstring pathname) { + if (pathname == NULL) { + throw_illegal_argument(env); + return; + } + + const char* c_pathname = (*env)->GetStringUTFChars(env, pathname, NULL); + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.pathname = ptr_to_u64(c_pathname); + attr.bpf_fd = fd; + + int res = sys_bpf(BPF_OBJ_PIN, &attr, sizeof(attr)); + if (res < 0) { + throw_io_exception(env); + } + + (*env)->ReleaseStringUTFChars(env, pathname, c_pathname); +} + +JNIEXPORT jstring JNICALL +Java_one_nio_os_bpf_Bpf_mapGetInfo(JNIEnv* env, jclass cls, int bpf_fd, jintArray result /*type,id,key_size,value_size,max_entries,flags*/) { + struct bpf_map_info info = {}; + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.info.bpf_fd = bpf_fd; + attr.info.info_len = sizeof(info); + attr.info.info = ptr_to_u64(&info); + + int res = sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr)); + if (res < 0) { + throw_io_exception(env); + return NULL; + } + + (*env)->SetIntArrayRegion(env, result, 0, 6, (int*)&info); + + return info.name[0] ? (*env)->NewStringUTF(env, info.name) : NULL; +} + +JNIEXPORT jstring JNICALL +Java_one_nio_os_bpf_Bpf_progGetInfo(JNIEnv* env, jclass cls, int bpf_fd, jintArray result /*type,id*/) { + struct bpf_prog_info info = {}; + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.info.bpf_fd = bpf_fd; + attr.info.info_len = sizeof(info); + attr.info.info = ptr_to_u64(&info); + + int res = sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr)); + if (res < 0) { + throw_io_exception(env); + return NULL; + } + + (*env)->SetIntArrayRegion(env, result, 0, 2, (int*)&info); + + return info.name[0] ? (*env)->NewStringUTF(env, info.name) : NULL; +} + +#define DEFAULT_MAP_IDS 64 + +static int __bpf_prog_get_map_ids(int bpf_fd, int* map_ids, int* num_maps) { + struct bpf_prog_info info = {}; + info.map_ids = ptr_to_u64(map_ids); + info.nr_map_ids = *num_maps; + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.info.bpf_fd = bpf_fd; + attr.info.info_len = sizeof(info); + attr.info.info = ptr_to_u64(&info); + + int res = sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr)); + if (res == 0) { + *num_maps = info.nr_map_ids; + } + return res; +} + +JNIEXPORT jintArray JNICALL +Java_one_nio_os_bpf_Bpf_progGetMapIds(JNIEnv* env, jclass cls, int bpf_fd) { + int map_ids[DEFAULT_MAP_IDS]; + int num_maps = DEFAULT_MAP_IDS; + + int res = __bpf_prog_get_map_ids(bpf_fd, map_ids, &num_maps); + if (res < 0) { + throw_io_exception(env); + return NULL; + } + + if (num_maps <= DEFAULT_MAP_IDS) { + jintArray result = (*env)->NewIntArray(env, num_maps); + if (result != NULL) { + (*env)->SetIntArrayRegion(env, result, 0, num_maps, map_ids); + } + return result; + } + + int map_ids2[num_maps]; + + res = __bpf_prog_get_map_ids(bpf_fd, map_ids2, &num_maps); + if (res < 0) { + throw_io_exception(env); + return NULL; + } + + jintArray result = (*env)->NewIntArray(env, num_maps); + if (result != NULL) { + (*env)->SetIntArrayRegion(env, result, 0, num_maps, map_ids2); + } + return result; +} + +JNIEXPORT jint JNICALL +Java_one_nio_os_bpf_Bpf_progGetFdById(JNIEnv* env, jclass cls, jint id) { + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.prog_id = id; + + int fd = sys_bpf(BPF_PROG_GET_FD_BY_ID, &attr, sizeof(attr)); + if (fd < 0) { + throw_io_exception(env); + } + return fd; +} + +JNIEXPORT jint JNICALL +Java_one_nio_os_bpf_Bpf_mapGetFdById(JNIEnv* env, jclass cls, jint id) { + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_id = id; + + int fd = sys_bpf(BPF_MAP_GET_FD_BY_ID, &attr, sizeof(attr)); + if (fd < 0) { + throw_io_exception(env); + } + return fd; +} + +JNIEXPORT jint JNICALL +Java_one_nio_os_bpf_Bpf_rawTracepointOpen(JNIEnv* env, jclass cls, jint prog_fd, jstring name) { +#if LINUX_VERSION_CODE >= 0x41100 + const char* c_name = (*env)->GetStringUTFChars(env, name, NULL); + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.raw_tracepoint.name = ptr_to_u64(c_name); + attr.raw_tracepoint.prog_fd = prog_fd; + + int fd = sys_bpf(BPF_RAW_TRACEPOINT_OPEN, &attr, sizeof(attr)); + if (fd < 0) { + throw_io_exception(env); + } + + (*env)->ReleaseStringUTFChars(env, name, c_name); + + return fd; +#else + throw_by_name(env, "java/lang/UnsupportedOperationException", "Library compiled without raw tracepoint support"); + return -EINVAL; +#endif +} + +JNIEXPORT jint JNICALL +Java_one_nio_os_bpf_Bpf_mapCreate(JNIEnv* env, jclass cls, jint type, jint key_size, jint value_size, + jint max_entries, jstring name, jint flags) { + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_type = type; + attr.key_size = key_size; + attr.value_size = value_size; + attr.max_entries = max_entries; + attr.map_flags = flags; + + if (name != NULL) { + jsize name_len = (*env)->GetStringUTFLength(env, name); + if (name_len >= BPF_OBJ_NAME_LEN) { + throw_illegal_argument_msg(env, "Too long name"); + return -EINVAL; + } + (*env)->GetStringUTFRegion(env, name, 0, name_len, attr.map_name); + } + + int fd = sys_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + if (fd < 0) { + throw_io_exception(env); + } + return fd; +} + +JNIEXPORT jboolean JNICALL +Java_one_nio_os_bpf_Bpf_mapLookup(JNIEnv* env, jclass cls, jint fd, jbyteArray key, jbyteArray result, jint flags) { + if (result == NULL) { + throw_illegal_argument(env); + return JNI_FALSE; + } + + jbyte* b_key = (*env)->GetByteArrayElements(env, key, NULL); + + const jsize result_len = (*env)->GetArrayLength(env, result); + jbyte b_result[result_len]; + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(b_key); + attr.value = ptr_to_u64(b_result); + attr.flags = flags; + + int res = sys_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); + if (res < 0 && errno != ENOENT) { + throw_io_exception(env); + } + + (*env)->ReleaseByteArrayElements(env, key, b_key, JNI_ABORT); + + if (res >= 0) { + (*env)->SetByteArrayRegion(env, result, 0, result_len, b_result); + return JNI_TRUE; + } + return JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_one_nio_os_bpf_Bpf_mapUpdate(JNIEnv* env, jclass cls, jint fd, jbyteArray key, jbyteArray value, jint flags) { + jbyte* b_key = (*env)->GetByteArrayElements(env, key, NULL); + jbyte* b_value = (*env)->GetByteArrayElements(env, value, NULL); + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(b_key); + attr.value = ptr_to_u64(b_value); + attr.flags = flags; + + int res = sys_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); + if (res != 0 && errno != EEXIST && errno != ENOENT) { + throw_io_exception(env); + } + + (*env)->ReleaseByteArrayElements(env, key, b_key, JNI_ABORT); + (*env)->ReleaseByteArrayElements(env, value, b_value, JNI_ABORT); + + return res == 0 ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_one_nio_os_bpf_Bpf_mapRemove(JNIEnv* env, jclass cls, jint fd, jbyteArray key) { + if (key == NULL) { + throw_illegal_argument(env); + return JNI_FALSE; + } + + jbyte* b_key = (*env)->GetByteArrayElements(env, key, NULL); + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(b_key); + + int res = sys_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); + if (res != 0 && errno != ENOENT) { + throw_io_exception(env); + } + + (*env)->ReleaseByteArrayElements(env, key, b_key, JNI_ABORT); + + return res == 0 ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_one_nio_os_bpf_Bpf_mapGetNextKey(JNIEnv* env, jclass cls, jint fd, jbyteArray key, jbyteArray next_key) { + if (next_key == NULL) { + throw_illegal_argument(env); + return JNI_FALSE; + } + + const jsize key_len = (*env)->GetArrayLength(env, next_key); + jbyte b_next_key[key_len]; + + jbyte* b_key = key != NULL ? (*env)->GetByteArrayElements(env, key, NULL) : NULL; + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(b_key); + attr.next_key = ptr_to_u64(b_next_key); + + int res = sys_bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); + if (res < 0 && errno != ENOENT) { + throw_io_exception(env); + } + + if (b_key != NULL) { + (*env)->ReleaseByteArrayElements(env, key, b_key, JNI_ABORT); + } + + if (res >= 0) { + (*env)->SetByteArrayRegion(env, next_key, 0, key_len, b_next_key); + return JNI_TRUE; + } + return JNI_FALSE; +} + +JNIEXPORT int JNICALL +Java_one_nio_os_bpf_Bpf_objGetNextId(JNIEnv* env, jclass cls, int objType, int startId) { + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.start_id = startId; + + int cmd = -1; + switch (objType) { + case 0: + cmd = BPF_PROG_GET_NEXT_ID; + break; + case 1: + cmd = BPF_MAP_GET_NEXT_ID; + break; + default: + throw_illegal_argument(env); + return -1; + } + + int res = sys_bpf(cmd, &attr, sizeof(attr)); + if (res >= 0) { + return attr.next_id; + } + + if (errno != ENOENT) { + throw_io_exception(env); + } + + return -1; +} diff --git a/src/one/nio/os/native/perf.c b/src/one/nio/os/native/perf.c index 9217001..9a18d8d 100644 --- a/src/one/nio/os/native/perf.c +++ b/src/one/nio/os/native/perf.c @@ -34,7 +34,13 @@ static const int ioctl_cmd[] = { PERF_EVENT_IOC_RESET, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_DISABLE, - PERF_EVENT_IOC_REFRESH + PERF_EVENT_IOC_REFRESH, + PERF_EVENT_IOC_PERIOD, + PERF_EVENT_IOC_SET_OUTPUT, + PERF_EVENT_IOC_SET_FILTER, + PERF_EVENT_IOC_ID, + PERF_EVENT_IOC_SET_BPF, + PERF_EVENT_IOC_PAUSE_OUTPUT }; static inline unsigned int get_uint_param(const char* op) { @@ -154,6 +160,24 @@ Java_one_nio_os_perf_Perf_get(JNIEnv* env, jclass cls, jint fd) { return counter; } +JNIEXPORT void JNICALL +Java_one_nio_os_perf_Perf_getValue(JNIEnv* env, jclass cls, jint fd, jlongArray result, jint offset, jint length) { + if (fd == -1) { + throw_channel_closed(env); + return; + } + + jlong buf[length]; + + size_t bytes_read = read(fd, buf, sizeof(buf)); + if (bytes_read < length * sizeof(jlong)) { + throw_io_exception(env); + return; + } + + (*env)->SetLongArrayRegion(env, result, offset, length, buf); +} + JNIEXPORT void JNICALL Java_one_nio_os_perf_Perf_ioctl(JNIEnv* env, jclass cls, jint fd, jint cmd, jint arg) { ioctl(fd, ioctl_cmd[cmd], arg); diff --git a/src/one/nio/os/perf/CounterValue.java b/src/one/nio/os/perf/CounterValue.java new file mode 100644 index 0000000..2521f9d --- /dev/null +++ b/src/one/nio/os/perf/CounterValue.java @@ -0,0 +1,27 @@ +/* + * 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.perf; + +public interface CounterValue { + double scalingFactor(); + + double runningFraction(); + + long normalized(); + + CounterValue sub(CounterValue prev); +} diff --git a/src/one/nio/os/perf/GlobalValue.java b/src/one/nio/os/perf/GlobalValue.java new file mode 100644 index 0000000..e6d293b --- /dev/null +++ b/src/one/nio/os/perf/GlobalValue.java @@ -0,0 +1,70 @@ +/* + * 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.perf; + +public class GlobalValue implements CounterValue { + private final long[] buffer; + + GlobalValue(long[] buffer) { + this.buffer = buffer; + } + + @Override + public double scalingFactor() { + return 1. / runningFraction(); + } + + @Override + public double runningFraction() { + long enabled = 0; + long running = 0; + for (int i = 0; i < buffer.length; i += 3) { + enabled += buffer[i + 1]; + running += buffer[i + 2]; + } + return enabled == running ? 1. : 1. * running / enabled; + } + + @Override + public long normalized() { + double total = 0; + for (int i = 0; i < buffer.length; i += 3) { + long value = buffer[i]; + long enabled = buffer[i + 1]; + long running = buffer[i + 2]; + if (enabled == running) { + total += value; + } else if (running > 0) { + total += (double) value * enabled / running; + } + } + return (long) total; + } + + @Override + public CounterValue sub(CounterValue prev) { + return sub((GlobalValue) prev); + } + + public GlobalValue sub(GlobalValue prev) { + long[] buffer = this.buffer.clone(); + for (int i = 0; i < buffer.length; i++) { + buffer[i] -= prev.buffer[i]; + } + return new GlobalValue(buffer); + } +} diff --git a/src/one/nio/os/perf/LocalValue.java b/src/one/nio/os/perf/LocalValue.java new file mode 100644 index 0000000..d250fbf --- /dev/null +++ b/src/one/nio/os/perf/LocalValue.java @@ -0,0 +1,53 @@ +/* + * 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.perf; + +public class LocalValue implements CounterValue { + public final long value; + public final long running; + public final long enabled; + + public LocalValue(long value, long running, long enabled) { + this.value = value; + this.running = running; + this.enabled = enabled; + } + + @Override + public double scalingFactor() { + return running == enabled ? 1. : (double) enabled / running; + } + + @Override + public double runningFraction() { + return running == enabled ? 1. : (double) running / enabled; + } + + @Override + public long normalized() { + return enabled == running ? value : (long) (value * scalingFactor()); + } + + @Override + public CounterValue sub(CounterValue prev) { + return sub((LocalValue) prev); + } + + public LocalValue sub(LocalValue v) { + return new LocalValue(value - v.value, running - v.running, enabled - v.enabled); + } +} diff --git a/src/one/nio/os/perf/Perf.java b/src/one/nio/os/perf/Perf.java index e383f10..5823659 100644 --- a/src/one/nio/os/perf/Perf.java +++ b/src/one/nio/os/perf/Perf.java @@ -16,34 +16,32 @@ package one.nio.os.perf; +import one.nio.os.Cpus; import one.nio.os.NativeLibrary; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.Native; -import java.nio.charset.StandardCharsets; import java.util.Arrays; public class Perf { public static final boolean IS_SUPPORTED = NativeLibrary.IS_SUPPORTED && new File("/proc/sys/kernel/perf_event_paranoid").exists(); - private static final int REAL_CPU_COUNT = getRealCpuCount(); - public static final int CURRENT_PID = 0; public static final int ANY_PID = -1; public static final int ANY_CPU = -1; public static PerfCounter open(PerfEvent event, int pid, int cpu, PerfOption... options) throws IOException { - if (cpu == ANY_CPU && (pid == ANY_PID || hasOption(options, PerfOption.PID_CGROUP))) { + if (cpu == ANY_CPU && (pid == ANY_PID || hasOption(options, PerfOption.PID_CGROUP) || option(options, PerfOption.GROUP_GLOBAL) != null)) { return openGlobal(event, pid, options); } int group = (int) optionValue(options, "GROUP"); + long readFormat = optionBits(options, "FORMAT"); int fd = openEvent(pid, cpu, event.type, event.config, event.breakpoint, group, optionString(options)); RingBuffer ringBuffer = createRingBuffer(fd, options); - return new PerfCounter(event, ringBuffer, fd); + return new PerfCounter(event, ringBuffer, readFormat, fd); } public static PerfCounter open(PerfEvent event, String cgroup, int cpu, PerfOption... options) throws IOException { @@ -58,13 +56,15 @@ public static PerfCounter open(PerfEvent event, String cgroup, int cpu, PerfOpti } } - private static PerfCounter openGlobal(PerfEvent event, int pid, PerfOption... options) throws IOException { + public static PerfCounterGlobal openGlobal(PerfEvent event, int pid, PerfOption... options) throws IOException { String optionString = optionString(options); - int[] fds = new int[REAL_CPU_COUNT]; + int[] fds = new int[Cpus.PRESENT]; + PerfOptionGlobalGroup group = (PerfOptionGlobalGroup) option(options, PerfOption.GROUP_GLOBAL); try { for (int cpu = 0; cpu < fds.length; cpu++) { - fds[cpu] = openEvent(pid, cpu, event.type, event.config, event.breakpoint, -1, optionString); + int groupFd = group == null ? -1 : group.fds[cpu]; + fds[cpu] = openEvent(pid, cpu, event.type, event.config, event.breakpoint, groupFd, optionString); } } catch (Throwable e) { for (int fd : fds) { @@ -75,7 +75,8 @@ private static PerfCounter openGlobal(PerfEvent event, int pid, PerfOption... op throw e; } - return new PerfCounterGlobal(event, fds); + long readFormat = optionBits(options, "FORMAT"); + return new PerfCounterGlobal(event, readFormat, fds); } private static RingBuffer createRingBuffer(int fd, PerfOption... options) throws IOException { @@ -118,51 +119,59 @@ private static String optionString(PerfOption[] options) { } private static long optionValue(PerfOption[] options, String name) { + PerfOption o = option(options, name); + return o == null ? -1 : o.value; + } + + private static PerfOption option(PerfOption[] options, String name) { for (PerfOption o : options) { if (o.name == name) { - return o.value; + return o; } } - return -1; + return null; } - private static boolean hasOption(PerfOption[] options, PerfOption option) { + private static long optionBits(PerfOption[] options, String name) { + long value = 0; for (PerfOption o : options) { - if (o == option) { - return true; + if (o.name == name) { + value |= o.value; } } - return false; + return value; } - private static int getRealCpuCount() { - try (FileInputStream in = new FileInputStream("/sys/devices/system/cpu/present")) { - byte[] buf = new byte[1024]; - int bytes = in.read(buf); - if (bytes > 0) { - String s = new String(buf, 0, bytes, StandardCharsets.ISO_8859_1).trim(); - int idx = Math.max(s.lastIndexOf('-'), s.lastIndexOf(',')); - return Integer.parseInt(s.substring(idx + 1)) + 1; + private static boolean hasOption(PerfOption[] options, PerfOption option) { + for (PerfOption o : options) { + if (o == option) { + return true; } - } catch (IOException e) { - // fall through } - return Runtime.getRuntime().availableProcessors(); + return false; } @Native static final int IOCTL_RESET = 0; @Native static final int IOCTL_ENABLE = 1; @Native static final int IOCTL_DISABLE = 2; @Native static final int IOCTL_REFRESH = 3; + @Native static final int IOCTL_PERIOD = 4; + @Native static final int IOCTL_SET_OUTPUT = 5; + @Native static final int IOCTL_SET_FILTER = 6; + @Native static final int IOCTL_ID = 7; + @Native static final int IOCTL_SET_BPF = 8; + @Native static final int IOCTL_PAUSE_OUTPUT = 9; static native int openEvent(int pid, int cpu, int type, long config, int breakpoint, int group, String options) throws IOException; - static native int openFile(String fileName) throws IOException; + public static native int openFile(String fileName) throws IOException; - static native void close(int fd); + public static native void close(int fd); static native long get(int fd) throws IOException; - static native void ioctl(int fd, int cmd, int arg); + static native void getValue(int fd, long[] value, int off, int len) throws IOException; + + static native void ioctl(int fd, int cmd, int arg) throws IOException; } diff --git a/src/one/nio/os/perf/PerfCounter.java b/src/one/nio/os/perf/PerfCounter.java index f794c44..e7270ff 100644 --- a/src/one/nio/os/perf/PerfCounter.java +++ b/src/one/nio/os/perf/PerfCounter.java @@ -23,15 +23,17 @@ public class PerfCounter implements Closeable { private final PerfEvent event; private final RingBuffer ringBuffer; + protected final long readFormat; volatile int fd; static final AtomicIntegerFieldUpdater fdUpdater = AtomicIntegerFieldUpdater.newUpdater(PerfCounter.class, "fd"); - PerfCounter(PerfEvent event, RingBuffer ringBuffer, int fd) { + PerfCounter(PerfEvent event, RingBuffer ringBuffer, long readFormat, int fd) { this.event = event; this.ringBuffer = ringBuffer; this.fd = fd; + this.readFormat = readFormat; } public final PerfEvent event() { @@ -53,6 +55,32 @@ public long get() throws IOException { return Perf.get(fd); } + public CounterValue getValue() throws IOException { + return toValue(getRawValue()); + } + + protected long[] getRawValue() throws IOException { + long[] buf = newBuffer(); + Perf.getValue(fd, buf, 0, buf.length); + return buf; + } + + protected long[] newBuffer() { + return new long[1 + Long.bitCount(readFormat)]; + } + + protected LocalValue toValue(long[] raw) { + long value = raw[0]; + int i = 1; + long enabled = hasReadFormat(ReadFormat.TOTAL_TIME_ENABLED) ? raw[i++] : 0; + long running = hasReadFormat(ReadFormat.TOTAL_TIME_RUNNING) ? raw[i] : 0; + return new LocalValue(value, running, enabled); + } + + public boolean hasReadFormat(int readFormat) { + return readFormat == (this.readFormat & readFormat); + } + public PerfSample nextSample() { if (ringBuffer == null) { throw new IllegalStateException("Not a sampling counter"); @@ -60,26 +88,30 @@ public PerfSample nextSample() { return ringBuffer.nextSample(); } - public void reset() { + public void reset() throws IOException { ioctl(Perf.IOCTL_RESET, 0); } - public void enable() { + public void attachBpf(int fd) throws IOException { + ioctl(Perf.IOCTL_SET_BPF, fd); + } + + public void enable() throws IOException { ioctl(Perf.IOCTL_ENABLE, 0); } - public void disable() { + public void disable() throws IOException { ioctl(Perf.IOCTL_DISABLE, 0); } - public void refresh(int count) { + public void refresh(int count) throws IOException { if (count < 0) { throw new IllegalArgumentException("count must be > 0"); } ioctl(Perf.IOCTL_REFRESH, count); } - void ioctl(int cmd, int arg) { + void ioctl(int cmd, int arg) throws IOException { Perf.ioctl(fd, cmd, arg); } } diff --git a/src/one/nio/os/perf/PerfCounterGlobal.java b/src/one/nio/os/perf/PerfCounterGlobal.java index f1f50f2..70039fe 100644 --- a/src/one/nio/os/perf/PerfCounterGlobal.java +++ b/src/one/nio/os/perf/PerfCounterGlobal.java @@ -16,13 +16,16 @@ package one.nio.os.perf; +import one.nio.os.bpf.BpfMap; +import one.nio.os.bpf.MapType; + import java.io.IOException; public class PerfCounterGlobal extends PerfCounter { - private final int[] fds; + final int[] fds; - PerfCounterGlobal(PerfEvent event, int[] fds) { - super(event, null, 0); + PerfCounterGlobal(PerfEvent event, long readFormat, int[] fds) { + super(event, null, readFormat, 0); this.fds = fds; } @@ -46,14 +49,58 @@ public long get() throws IOException { return sum; } + @Override + public CounterValue getValue() throws IOException { + int vals = hasReadFormat(ReadFormat.TOTAL_TIME_RUNNING | ReadFormat.TOTAL_TIME_ENABLED) ? 3 : 1; + long[] buf = new long[3 * fds.length]; + for (int cpu = 0; cpu < fds.length; cpu++) { + Perf.getValue(fds[cpu], buf, cpu * 3, vals); + } + return new GlobalValue(buf); + } + public long getForCpu(int cpu) throws IOException { return Perf.get(fds[cpu]); } + public LocalValue getValueForCpu(int cpu) throws IOException { + long[] buf = newBuffer(); + Perf.getValue(fds[cpu], buf, 0, buf.length); + return toValue(buf); + } + + @Override + protected long[] getRawValue() throws IOException { + long[] buf = newBuffer(); + long[] total = newBuffer(); + for (int fd : fds) { + Perf.getValue(fd, buf, 0, buf.length); + for (int i = 0; i < buf.length; i++) { + total[i] += buf[i]; + } + } + return total; + } + @Override - void ioctl(int cmd, int arg) { + void ioctl(int cmd, int arg) throws IOException { for (int fd : fds) { Perf.ioctl(fd, cmd, arg); } } + + public void storeTo(BpfMap map) throws IOException { + assert fds.length == BpfMap.CPUS : "Are some cpus offline?"; + for (int i = 0; i < fds.length; i++) { + storeTo(map, i); + } + } + + public void storeTo(BpfMap map, int cpu) throws IOException { + if (map.type != MapType.PERF_EVENT_ARRAY) { + throw new IllegalArgumentException(); + } + + map.put(BpfMap.bytes(cpu), BpfMap.bytes(fds[cpu])); + } } diff --git a/src/one/nio/os/perf/PerfEvent.java b/src/one/nio/os/perf/PerfEvent.java index c118614..5ecd0b3 100644 --- a/src/one/nio/os/perf/PerfEvent.java +++ b/src/one/nio/os/perf/PerfEvent.java @@ -27,7 +27,7 @@ public class PerfEvent implements Serializable { private static final int PERF_TYPE_SOFTWARE = 1; private static final int PERF_TYPE_TRACEPOINT = 2; private static final int PERF_TYPE_HW_CACHE = 3; - private static final int PERF_TYPE_RAW = 4; + private static final int PERF_TYPE_RAW_CPU = 4; private static final int PERF_TYPE_BREAKPOINT = 5; public static final PerfEvent @@ -102,7 +102,11 @@ public static PerfEvent cache(CacheType type, CacheOp op) { } public static PerfEvent raw(long config) { - return new PerfEvent("RAW:" + Long.toHexString(config), PERF_TYPE_RAW, config); + return raw(PERF_TYPE_RAW_CPU, config); + } + + public static PerfEvent raw(int type, long config) { + return new PerfEvent("RAW:" + type + ":" + Long.toHexString(config), type, config); } public static PerfEvent tracepoint(int id) { @@ -133,4 +137,8 @@ public static PerfEvent breakpoint(BreakpointType type, int len, long addr) { return new PerfEvent("BREAKPOINT:" + type.name() + ':' + Long.toHexString(addr), PERF_TYPE_BREAKPOINT, addr, type.ordinal() | len << 8); } + + public static int getEventType(String name) throws IOException { + return Integer.parseInt(Files.readAllLines(Paths.get("/sys/bus/event_source/devices/" + name + "/type")).get(0).trim()); + } } diff --git a/src/one/nio/os/perf/PerfOption.java b/src/one/nio/os/perf/PerfOption.java index af5ef65..834f164 100644 --- a/src/one/nio/os/perf/PerfOption.java +++ b/src/one/nio/os/perf/PerfOption.java @@ -52,16 +52,17 @@ public class PerfOption implements Serializable { FORMAT_GROUP = format(ReadFormat.GROUP); static final PerfOption PID_CGROUP = new PerfOption("PID_CGROUP"); + static final String GROUP_GLOBAL = "GROUP_GLOBAL"; final String name; final long value; - private PerfOption(String name) { + protected PerfOption(String name) { this.name = name; this.value = -1; } - private PerfOption(String name, long value) { + protected PerfOption(String name, long value) { this.name = name; this.value = value; } @@ -118,6 +119,8 @@ public static PerfOption pages(int pages) { } public static PerfOption group(PerfCounter leader) { - return new PerfOption("GROUP", leader.fd); + return leader instanceof PerfCounterGlobal + ? new PerfOptionGlobalGroup(GROUP_GLOBAL, ((PerfCounterGlobal) leader).fds) + : new PerfOption("GROUP", leader.fd); } } diff --git a/src/one/nio/os/perf/PerfOptionGlobalGroup.java b/src/one/nio/os/perf/PerfOptionGlobalGroup.java new file mode 100644 index 0000000..3548425 --- /dev/null +++ b/src/one/nio/os/perf/PerfOptionGlobalGroup.java @@ -0,0 +1,26 @@ +/* + * 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.perf; + +class PerfOptionGlobalGroup extends PerfOption { + final int[] fds; + + protected PerfOptionGlobalGroup(String name, int[] fds) { + super(name); + this.fds = fds; + } +} diff --git a/test/one/nio/os/bpf/BpfListTest.java b/test/one/nio/os/bpf/BpfListTest.java new file mode 100644 index 0000000..30c9c7d --- /dev/null +++ b/test/one/nio/os/bpf/BpfListTest.java @@ -0,0 +1,34 @@ +/* + * 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 java.io.IOException; +import java.util.Arrays; + +public class BpfListTest { + public static void main(String[] args) throws IOException { + for (int id : BpfProg.getAllIds()) { + BpfProg prog = BpfProg.getById(id); + System.out.printf("Prog %d: %s %s, maps: %s %n", prog.id, prog.name, prog.type, Arrays.toString(prog.getMapIds())); + } + + for (int id : BpfMap.getAllIds()) { + BpfMap map = BpfMap.getById(id); + System.out.printf("Map %d: %s %s, key size: %d, value size: %d, max elements: %d, flags: %d%n", map.id, map.name, map.type, map.keySize, map.valueSize, map.maxEntries, map.flags); + } + } +} diff --git a/test/one/nio/os/bpf/BpfMapDumpTest.java b/test/one/nio/os/bpf/BpfMapDumpTest.java new file mode 100644 index 0000000..c694269 --- /dev/null +++ b/test/one/nio/os/bpf/BpfMapDumpTest.java @@ -0,0 +1,60 @@ +/* + * 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 java.nio.LongBuffer; + +public class BpfMapDumpTest { + + public static void main(String[] args) throws Exception { + String file; + if (args.length == 0) { + file = "/sys/fs/bpf/map"; + BpfMap map = BpfMap.newMap(MapType.PERCPU_HASH, 4, 8, 256, "test", 0); + byte[] value = new byte[map.totalValueSize]; + BpfMap.longs(value).put(1); + map.putIfAbsent(BpfMap.bytes(1), value); + map.pin(file); + map.close(); + } else { + file = args[0]; + } + + BpfMap map; + try { + int id = Integer.parseInt(file); + map = BpfMap.getById(id); + } catch (NumberFormatException e) { + map = BpfMap.getPinned(file); + } + + byte[] result = new byte[map.totalValueSize]; + LongBuffer values = BpfMap.longs(result); + for (int i = 0; ; i++) { + System.out.println(i); + for (byte[] key : map.keys()) { + if (map.get(key, result)) { + values.rewind(); + long cnt = 0; + while (values.hasRemaining()) cnt += values.get(); + System.out.printf("%10d: %d\n", BpfMap.ints(key).get(), cnt); + } + } + Thread.sleep(1000); + } + } +} diff --git a/test/one/nio/os/bpf/BpfMapTest.java b/test/one/nio/os/bpf/BpfMapTest.java new file mode 100644 index 0000000..87cf418 --- /dev/null +++ b/test/one/nio/os/bpf/BpfMapTest.java @@ -0,0 +1,104 @@ +/* + * 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 java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; + +import org.junit.Test; + +public class BpfMapTest { + @Test + public void testMap() throws IOException { + BpfMap map = BpfMap.newMap(MapType.HASH, 4, 4, 2, null, 0); + assert map.get(BpfMap.bytes(1)) == null; + + byte[] b1 = BpfMap.bytes(1); + byte[] b2 = BpfMap.bytes(2); + byte[] b3 = BpfMap.bytes(3); + + assert map.put(b1, b2); + assert map.get(b1)[0] == 2; + assert map.get(b2) == null; + + assert map.put(b2, b3); + assert map.get(b1)[0] == 2; + assert map.get(b2)[0] == 3; + + try { + map.put(b3, b3); + assert false : "E2BIG expected"; + } catch (IOException ignored) { + assert map.get(b3) == null; + } + + assert map.remove(b1); + assert map.get(b1) == null; + + assert !map.putIfPresent(b1, b1); + assert map.get(b1) == null; + + assert map.putIfAbsent(b1, b1); + assert map.get(b1)[0] == 1; + + assert !map.putIfAbsent(b1, b2); + assert map.get(b1)[0] == 1; + + assert map.put(b1, b2); + assert map.get(b1)[0] == 2; + + assert !map.putIfAbsent(b1, b3); + assert map.get(b1)[0] == 2; + + assert map.putIfPresent(b1, b3); + assert map.get(b1)[0] == 3; + + assert map.remove(b1); + assert map.get(b1) == null; + + assert !map.remove(b1); + + map.close(); + } + + @Test + public void testPerCpuMap() throws IOException { + BpfMap map = BpfMap.newMap(MapType.PERCPU_HASH, 4, 8, 1, null, 0); + assert map.totalValueSize == 8 * BpfMap.CPUS; + assert map.get(BpfMap.bytes(1)) == null; + + ByteBuffer buf = ByteBuffer.allocate(map.totalValueSize); + LongBuffer lbuf = buf.asLongBuffer(); + for (int i = 0; i < BpfMap.CPUS; i++) { + lbuf.put(i); + } + + map.put(BpfMap.bytes(1), buf.array()); + + byte[] value = map.get(BpfMap.bytes(1)); + LongBuffer lbuf2 = BpfMap.longs(value); + for (int i = 0; i < BpfMap.CPUS; i++) { + assert i == lbuf2.get(); + } + + map.remove(BpfMap.bytes(1)); + assert map.get(BpfMap.bytes(1)) == null; + + map.close(); + } +} diff --git a/test/one/nio/os/bpf/BpfProgTest.java b/test/one/nio/os/bpf/BpfProgTest.java new file mode 100644 index 0000000..03c1be1 --- /dev/null +++ b/test/one/nio/os/bpf/BpfProgTest.java @@ -0,0 +1,48 @@ +/* + * 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 java.io.IOException; + +public class BpfProgTest { + public static void main(String[] args) throws IOException { + BpfProg prog = BpfProg.load(args[0], ProgType.UNSPEC); + String pinPath = args.length > 1 ? args[1] : null; + + System.out.printf("Loaded %s %s id:%d%n", prog.type, prog.name, prog.id); + + if (pinPath != null) { + System.out.printf("Pinning prog to %s%n", pinPath + prog.name); + prog.pin(pinPath + prog.name); + } + + int[] mapIds = prog.getMapIds(); + System.out.printf("Found %d maps%n", mapIds.length); + + for (int id : mapIds) { + BpfMap map = BpfMap.getById(id); + System.out.printf("%s %s id:%d key:%d value:%d max:%d%n", map.type, map.name, map.id, map.keySize, map.valueSize, map.maxEntries); + if (pinPath != null) { + System.out.printf("Pinning map to %s%n", pinPath + map.name); + map.pin(pinPath + map.name); + } + map.close(); + } + + prog.close(); + } +} diff --git a/test/one/nio/os/perf/BrokenCounterTest.java b/test/one/nio/os/perf/BrokenCounterTest.java new file mode 100644 index 0000000..30ab2f3 --- /dev/null +++ b/test/one/nio/os/perf/BrokenCounterTest.java @@ -0,0 +1,38 @@ +/* + * 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.perf; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +public class BrokenCounterTest { + public static void main(String[] args) throws IOException, InterruptedException { + PerfCounter counter = Perf.open(PerfEvent.HW_CPU_CYCLES, Perf.ANY_PID, 0); + + long c = counter.get(); + while (!Thread.interrupted()) { + long v = counter.get(); + if (0 != (c ^ v) >> 46) { + System.out.printf("%s %x -> %x (+%x)\n", DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(Instant.now().atZone(ZoneId.systemDefault())), c, v, v - c); + } + c = v; + Thread.sleep(10L); + } + } +} diff --git a/test/one/nio/os/perf/PerfStat.java b/test/one/nio/os/perf/PerfStat.java new file mode 100644 index 0000000..763d558 --- /dev/null +++ b/test/one/nio/os/perf/PerfStat.java @@ -0,0 +1,289 @@ +/* + * 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.perf; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Java-based analogue of perf stat. + */ +public class PerfStat { + + private static long nanos; + private static Counter cpu_clock; + + public static void main(String[] args) throws Exception { + long time = 3600000, interval = 0; + int detailLevel = 0; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + switch (arg) { + case "--help": + printHelp(); + return; + case "-dddd": + detailLevel++; + case "-ddd": + detailLevel++; + case "-dd": + detailLevel++; + case "-d": + detailLevel++; + continue; + } + if (!arg.startsWith("-")) { + System.err.println("Unexpected argument: " + arg); + printHelp(); + return; + } + if (++i == args.length) { + System.err.println("Expected value for: " + arg); + printHelp(); + return; + } + String value = args[i]; + switch (arg) { + case "-t": + case "--time": + time = Long.parseLong(value); + break; + case "-I": + case "--interval": + interval = Long.parseLong(value); + break; + default: + System.err.println("Unknown parameter: " + arg); + printHelp(); + return; + } + } + + Counter c; + List counters = new ArrayList<>(); + counters.addAll(Arrays.asList( + cpu_clock = new Counter("cpu-clock", PerfEvent.SW_CPU_CLOCK) { + @Override + protected String format(long val) { + return String.format("%,18.2f msec %-25s # % 7.3f CPUs utilized", val / 1_000_000., name, (double) val / nanos); + } + }, + new Counter("context-switches", PerfEvent.SW_CONTEXT_SWITCHES), + new Counter("cpu-migrations", PerfEvent.SW_CPU_MIGRATIONS), + new Counter("page-faults", PerfEvent.SW_PAGE_FAULTS), + c = new Counter("cycles", PerfEvent.HW_CPU_CYCLES) { + @Override + protected String format(long val) { + return String.format("%,18d %-25s # %8.3f GHz", val, name, (double) val / cpu_clock.value.normalized()); + } + }, + new Counter("instructions", PerfEvent.HW_INSTRUCTIONS, c, "insn per cycle") { + protected String format(long val, double frac) { + return String.format("%,18d %-25s # %8.3f %-25s", val, name, frac, fracName); + } + }, + c = new Counter("branches", PerfEvent.HW_BRANCH_INSTRUCTIONS), + new Counter("branch-misses", PerfEvent.HW_BRANCH_MISSES, c, "of all branches") + )); + if (detailLevel > 0) { + counters.addAll(Arrays.asList( + c = new Counter("L1-dcache-loads", PerfEvent.cache(CacheType.L1D, CacheOp.READ)), + new Counter("L1-dcache-load-misses", PerfEvent.cache(CacheType.L1D, CacheOp.READ_MISS), c, "of all L1-dcache hits"), + c = new Counter("LLC-loads", PerfEvent.cache(CacheType.LL, CacheOp.READ)), + new Counter("LLC-load-misses", PerfEvent.cache(CacheType.LL, CacheOp.READ_MISS), c, "of all LL-cache hits") + )); + } + if (detailLevel > 1) { + counters.addAll(Arrays.asList( + c = new Counter("dTLB-loads", PerfEvent.cache(CacheType.DTLB, CacheOp.READ)), + new Counter("dTLB-load-misses", PerfEvent.cache(CacheType.DTLB, CacheOp.READ_MISS), c, "of all dTLB hits"), + c = new Counter("dTLB-loads", PerfEvent.cache(CacheType.ITLB, CacheOp.READ)), + new Counter("iTLB-load-misses", PerfEvent.cache(CacheType.ITLB, CacheOp.READ_MISS), c, "of all iTLB hits") + )); + } + if (detailLevel > 2) { + counters.addAll(Arrays.asList( + c = new Counter("L1-dcache-prefetches", PerfEvent.cache(CacheType.L1D, CacheOp.PREFETCH)), + new Counter("L1-dcache-prefetch-misses", PerfEvent.cache(CacheType.L1D, CacheOp.PREFETCH_MISS), c, "of all L1-dcache prefetches") + )); + } + if (detailLevel > 3) { + counters.addAll(Arrays.asList( + new Counter("bus-cycles", PerfEvent.HW_BUS_CYCLES), + new Counter("ref-cycles", PerfEvent.HW_REF_CPU_CYCLES), + new Counter("major-faults", PerfEvent.SW_PAGE_FAULTS_MIN), + new Counter("minor-faults", PerfEvent.SW_PAGE_FAULTS_MIN), + new Counter("cpu-clock", PerfEvent.SW_CPU_CLOCK), + new Counter("task-clock", PerfEvent.SW_TASK_CLOCK) + )); + } + + counters.forEach(Counter::start); + + Thread t = Thread.currentThread(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + t.interrupt(); + t.join(); + } catch (InterruptedException ignored) { + } + })); + + long start = System.nanoTime(); + long cycleStart = System.nanoTime(); + + boolean run = interval > 0; + do { + try { + Thread.sleep(interval > 0 ? interval : time); + } catch (InterruptedException ignored) { + run = false; + } + + long now = System.nanoTime(); + nanos = now - cycleStart; + cycleStart = now; + run &= now - start < TimeUnit.MILLISECONDS.toNanos(time); + + counters.forEach(Counter::measure); + if (interval == 0) { + System.out.println("\n Performance counter stats for 'system wide':\n"); + } + + counters.forEach(System.out::println); + + if (interval == 0) { + System.out.printf("\n%18.9f seconds time elapsed\n\n", nanos / 1_000_000_000.); + } + } while (run); + + counters.forEach(Counter::stop); + } + + private static void printHelp() { + System.out.println("perf-stat - Gather performance counter statistics"); + System.out.println("Arguments:"); + System.out.println("\t-time , -t "); + System.out.println("\t\tduration in msecs"); + System.out.println(); + System.out.println("\t-interval , -I "); + System.out.println("\t\tperiod in msecs"); + System.out.println(); + System.out.println("\t-d, -dd, -ddd, -dddd"); + System.out.println("\t\tlevel of detail"); + } + + static class Counter { + String name; + Counter ref; + String fracName; + PerfCounter counter; + CounterValue base; + CounterValue value; + + Counter(String name, PerfEvent event) { + this.name = name; + try { + counter = Perf.open(event, Perf.ANY_PID, Perf.ANY_CPU, + PerfOption.format(ReadFormat.TOTAL_TIME_ENABLED), + PerfOption.format(ReadFormat.TOTAL_TIME_RUNNING) + ); + } catch (IOException ignored) { + } + } + + Counter(String name, PerfEvent event, Counter ref, String fracName) { + this.name = name; + this.ref = ref; + this.fracName = fracName; + try { + if (ref.counter == null) return; + counter = Perf.open(event, Perf.ANY_PID, Perf.ANY_CPU, + PerfOption.group(ref.counter), + PerfOption.format(ReadFormat.TOTAL_TIME_ENABLED), + PerfOption.format(ReadFormat.TOTAL_TIME_RUNNING) + ); + } catch (IOException ignored) { + } + } + + public void start() { + if (counter != null) { + try { + if (ref == null) counter.enable(); + base = counter.getValue(); + } catch (IOException e) { + e.printStackTrace(); + counter = null; + } + } + } + + public void measure() { + try { + if (counter != null) { + CounterValue cur = counter.getValue(); + this.value = cur.sub(base); + base = cur; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void stop() { + try { + if (counter != null) { + counter.disable(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public String toString() { + if (value != null) { + String s = ref != null && ref.value != null + ? format(value.normalized(), (double) value.normalized() / ref.value.normalized()) + : format(value.normalized()); + return value.runningFraction() == 1. ? s + : String.format("%-87s (%.2f%%)", s, 100 * value.runningFraction()); + } else { + return String.format("%18s %-25s", "", name); + } + } + + static final String[] C = new String[]{"B", "M", "K", ""}; + + protected String format(long val) { + int i = 0; + double freq = 1. * val / cpu_clock.value.normalized(); + while (i < C.length - 1 && freq < 1) { + i++; + freq *= 1000; + } + return String.format("%,18d %-25s # %8.3f %s/sec", val, name, freq, C[i]); + } + + protected String format(long val, double frac) { + return String.format("%,18d %-25s # %8.3f%% %-25s", val, name, 100 * frac, fracName); + } + } +} diff --git a/test/one/nio/os/perf/RawAMDL3MissLatency.java b/test/one/nio/os/perf/RawAMDL3MissLatency.java new file mode 100644 index 0000000..d21ed18 --- /dev/null +++ b/test/one/nio/os/perf/RawAMDL3MissLatency.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 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.perf; + +import java.io.IOException; + +/** + * Computes Average L3 Read Miss Latency (in core clocks) on AMD processors. + * + * l3_read_miss_latency = (xi_sys_fill_latency * 16) / xi_ccx_sdp_req1.all_l3_miss_req_typs + */ +public class RawAMDL3MissLatency { + + public static void main(String[] args) throws Exception { + int amdL3EventType; + try { + amdL3EventType = PerfEvent.getEventType("amd_l3"); + } catch (Exception e) { + throw new IOException("Failed to read amd_l3 event type. Not an AMD cpu?", e); + } + + PerfOption format = PerfOption.format(ReadFormat.TOTAL_TIME_RUNNING | ReadFormat.TOTAL_TIME_ENABLED); + // xi_sys_fill_latency: L3 Cache Miss Latency. Total cycles for all transactions divided by 16. Ignores SliceMask and ThreadMask. + PerfCounterGlobal samplerLatency = Perf.openGlobal(PerfEvent.raw(amdL3EventType, 0x0090), Perf.ANY_PID, format); + // xi_ccx_sdp_req1.all_l3_miss_req_typs: All L3 Miss Request Types. Ignores SliceMask and ThreadMask. + PerfCounterGlobal samplerCount = Perf.openGlobal(PerfEvent.raw(amdL3EventType, 0x3f9a), Perf.ANY_PID, PerfOption.group(samplerLatency), format); + + samplerLatency.enable(); + + CounterValue prevLatency = null; + CounterValue prevCount = null; + + for (int i = 0; ; i++) { + + CounterValue curLatency = samplerLatency.getValue(); + CounterValue curCount = samplerCount.getValue(); + + if (prevLatency != null && prevCount != null) { + CounterValue deltaLatency = curLatency.sub(prevLatency); + CounterValue deltaCount = curCount.sub(prevCount); + + double latency = deltaLatency.normalized() * 16; + double count = deltaCount.normalized(); + + System.out.printf("%d. miss count: %,d; total latency in cpu clocks: %,d; average latency in cpu clocks: %.2f (upscaled from %.2f%% samples)%n", + i, (long) count, (long) latency, count > 0 ? latency / count : 0., 100. * deltaLatency.runningFraction() + ); + } + + prevLatency = curLatency; + prevCount = curCount; + + Thread.sleep(1000); + } + } + +}