From 759d1773efbc03bcf9005f46d5d53c9d3a31c654 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Fri, 18 Oct 2024 19:33:20 +0000 Subject: [PATCH 01/22] wip on #4973 --- core/pom.xml | 35 + .../core/metrics/flatbuffers/FMetric.java | 103 ++ .../core/metrics/flatbuffers/FTag.java | 79 + core/src/main/flatbuffers/metric.fbs | 35 + .../accumulo/core/metrics/MetricsInfo.java | 20 +- .../core/rpc/clients/MetricsThriftClient.java | 29 + .../core/rpc/clients/ThriftClientTypes.java | 2 + core/src/main/scripts/generate-thrift.sh | 2 +- .../core/metrics/thrift/MetricResponse.java | 887 ++++++++++++ .../core/metrics/thrift/MetricService.java | 1273 +++++++++++++++++ .../core/metrics/thrift/MetricSource.java | 70 + core/src/main/thrift/metrics.thrift | 50 + pom.xml | 36 + .../accumulo/server/AbstractServer.java | 6 + .../server/metrics/MetricResponseWrapper.java | 182 +++ .../server/metrics/MetricServiceHandler.java | 98 ++ .../server/rpc/ThriftProcessorTypes.java | 29 +- .../apache/accumulo/compactor/Compactor.java | 6 +- .../accumulo/gc/SimpleGarbageCollector.java | 6 +- .../org/apache/accumulo/manager/Manager.java | 6 +- .../apache/accumulo/tserver/ScanServer.java | 8 +- .../apache/accumulo/tserver/TabletServer.java | 10 +- .../accumulo/test/metrics/MetricsIT.java | 2 +- .../test/metrics/MetricsThriftRPCIT.java | 78 + 24 files changed, 3030 insertions(+), 22 deletions(-) create mode 100644 core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FMetric.java create mode 100644 core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FTag.java create mode 100644 core/src/main/flatbuffers/metric.fbs create mode 100644 core/src/main/java/org/apache/accumulo/core/rpc/clients/MetricsThriftClient.java create mode 100644 core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricResponse.java create mode 100644 core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricService.java create mode 100644 core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricSource.java create mode 100644 core/src/main/thrift/metrics.thrift create mode 100644 server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java create mode 100644 server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java create mode 100644 test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java diff --git a/core/pom.xml b/core/pom.xml index a55ec1c4a3c..0beba5a03f9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -51,6 +51,10 @@ com.google.code.gson gson + + com.google.flatbuffers + flatbuffers-java + com.google.guava guava @@ -412,6 +416,37 @@ + + flatbuffers + + + + com.sequsoft.maven.plugins + flatbuffers-maven-plugin + 0.0.1 + + + compile-flatbuffers + + compile-flatbuffers + + generate-sources + + ${version.flatbuffers} + + ${basedir}/src/main/flatbuffers/metric.fbs + + + all + + ${basedir}/src/main/flatbuffers-gen-java + + + + + + + thrift diff --git a/core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FMetric.java b/core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FMetric.java new file mode 100644 index 00000000000..36145ba18dc --- /dev/null +++ b/core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FMetric.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +// automatically generated by the FlatBuffers compiler, do not modify + +package org.apache.accumulo.core.metrics.flatbuffers; + +import com.google.flatbuffers.BaseVector; +import com.google.flatbuffers.BooleanVector; +import com.google.flatbuffers.ByteVector; +import com.google.flatbuffers.Constants; +import com.google.flatbuffers.DoubleVector; +import com.google.flatbuffers.FlatBufferBuilder; +import com.google.flatbuffers.FloatVector; +import com.google.flatbuffers.IntVector; +import com.google.flatbuffers.LongVector; +import com.google.flatbuffers.ShortVector; +import com.google.flatbuffers.StringVector; +import com.google.flatbuffers.Struct; +import com.google.flatbuffers.Table; +import com.google.flatbuffers.UnionVector; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@SuppressWarnings("unused") +public final class FMetric extends Table { + public static void ValidateVersion() { Constants.FLATBUFFERS_24_3_25(); } + public static FMetric getRootAsFMetric(ByteBuffer _bb) { return getRootAsFMetric(_bb, new FMetric()); } + public static FMetric getRootAsFMetric(ByteBuffer _bb, FMetric obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); } + public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); } + public FMetric __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public String name() { int o = __offset(4); return o != 0 ? __string(o + bb_pos) : null; } + public ByteBuffer nameAsByteBuffer() { return __vector_as_bytebuffer(4, 1); } + public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 4, 1); } + public String type() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; } + public ByteBuffer typeAsByteBuffer() { return __vector_as_bytebuffer(6, 1); } + public ByteBuffer typeInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 6, 1); } + public org.apache.accumulo.core.metrics.flatbuffers.FTag tags(int j) { return tags(new org.apache.accumulo.core.metrics.flatbuffers.FTag(), j); } + public org.apache.accumulo.core.metrics.flatbuffers.FTag tags(org.apache.accumulo.core.metrics.flatbuffers.FTag obj, int j) { int o = __offset(8); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; } + public int tagsLength() { int o = __offset(8); return o != 0 ? __vector_len(o) : 0; } + public org.apache.accumulo.core.metrics.flatbuffers.FTag.Vector tagsVector() { return tagsVector(new org.apache.accumulo.core.metrics.flatbuffers.FTag.Vector()); } + public org.apache.accumulo.core.metrics.flatbuffers.FTag.Vector tagsVector(org.apache.accumulo.core.metrics.flatbuffers.FTag.Vector obj) { int o = __offset(8); return o != 0 ? obj.__assign(__vector(o), 4, bb) : null; } + public double dvalue() { int o = __offset(10); return o != 0 ? bb.getDouble(o + bb_pos) : 0.0; } + public int ivalue() { int o = __offset(12); return o != 0 ? bb.getInt(o + bb_pos) : 0; } + public long lvalue() { int o = __offset(14); return o != 0 ? bb.getLong(o + bb_pos) : 0L; } + + public static int createFMetric(FlatBufferBuilder builder, + int nameOffset, + int typeOffset, + int tagsOffset, + double dvalue, + int ivalue, + long lvalue) { + builder.startTable(6); + FMetric.addLvalue(builder, lvalue); + FMetric.addDvalue(builder, dvalue); + FMetric.addIvalue(builder, ivalue); + FMetric.addTags(builder, tagsOffset); + FMetric.addType(builder, typeOffset); + FMetric.addName(builder, nameOffset); + return FMetric.endFMetric(builder); + } + + public static void startFMetric(FlatBufferBuilder builder) { builder.startTable(6); } + public static void addName(FlatBufferBuilder builder, int nameOffset) { builder.addOffset(0, nameOffset, 0); } + public static void addType(FlatBufferBuilder builder, int typeOffset) { builder.addOffset(1, typeOffset, 0); } + public static void addTags(FlatBufferBuilder builder, int tagsOffset) { builder.addOffset(2, tagsOffset, 0); } + public static int createTagsVector(FlatBufferBuilder builder, int[] data) { builder.startVector(4, data.length, 4); for (int i = data.length - 1; i >= 0; i--) builder.addOffset(data[i]); return builder.endVector(); } + public static void startTagsVector(FlatBufferBuilder builder, int numElems) { builder.startVector(4, numElems, 4); } + public static void addDvalue(FlatBufferBuilder builder, double dvalue) { builder.addDouble(3, dvalue, 0.0); } + public static void addIvalue(FlatBufferBuilder builder, int ivalue) { builder.addInt(4, ivalue, 0); } + public static void addLvalue(FlatBufferBuilder builder, long lvalue) { builder.addLong(5, lvalue, 0L); } + public static int endFMetric(FlatBufferBuilder builder) { + int o = builder.endTable(); + return o; + } + public static void finishFMetricBuffer(FlatBufferBuilder builder, int offset) { builder.finish(offset); } + public static void finishSizePrefixedFMetricBuffer(FlatBufferBuilder builder, int offset) { builder.finishSizePrefixed(offset); } + + public static final class Vector extends BaseVector { + public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { __reset(_vector, _element_size, _bb); return this; } + + public FMetric get(int j) { return get(new FMetric(), j); } + public FMetric get(FMetric obj, int j) { return obj.__assign(__indirect(__element(j), bb), bb); } + } +} + diff --git a/core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FTag.java b/core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FTag.java new file mode 100644 index 00000000000..3581660b9b1 --- /dev/null +++ b/core/src/main/flatbuffers-gen-java/org/apache/accumulo/core/metrics/flatbuffers/FTag.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +// automatically generated by the FlatBuffers compiler, do not modify + +package org.apache.accumulo.core.metrics.flatbuffers; + +import com.google.flatbuffers.BaseVector; +import com.google.flatbuffers.BooleanVector; +import com.google.flatbuffers.ByteVector; +import com.google.flatbuffers.Constants; +import com.google.flatbuffers.DoubleVector; +import com.google.flatbuffers.FlatBufferBuilder; +import com.google.flatbuffers.FloatVector; +import com.google.flatbuffers.IntVector; +import com.google.flatbuffers.LongVector; +import com.google.flatbuffers.ShortVector; +import com.google.flatbuffers.StringVector; +import com.google.flatbuffers.Struct; +import com.google.flatbuffers.Table; +import com.google.flatbuffers.UnionVector; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@SuppressWarnings("unused") +public final class FTag extends Table { + public static void ValidateVersion() { Constants.FLATBUFFERS_24_3_25(); } + public static FTag getRootAsFTag(ByteBuffer _bb) { return getRootAsFTag(_bb, new FTag()); } + public static FTag getRootAsFTag(ByteBuffer _bb, FTag obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); } + public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); } + public FTag __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public String key() { int o = __offset(4); return o != 0 ? __string(o + bb_pos) : null; } + public ByteBuffer keyAsByteBuffer() { return __vector_as_bytebuffer(4, 1); } + public ByteBuffer keyInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 4, 1); } + public String value() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; } + public ByteBuffer valueAsByteBuffer() { return __vector_as_bytebuffer(6, 1); } + public ByteBuffer valueInByteBuffer(ByteBuffer _bb) { return __vector_in_bytebuffer(_bb, 6, 1); } + + public static int createFTag(FlatBufferBuilder builder, + int keyOffset, + int valueOffset) { + builder.startTable(2); + FTag.addValue(builder, valueOffset); + FTag.addKey(builder, keyOffset); + return FTag.endFTag(builder); + } + + public static void startFTag(FlatBufferBuilder builder) { builder.startTable(2); } + public static void addKey(FlatBufferBuilder builder, int keyOffset) { builder.addOffset(0, keyOffset, 0); } + public static void addValue(FlatBufferBuilder builder, int valueOffset) { builder.addOffset(1, valueOffset, 0); } + public static int endFTag(FlatBufferBuilder builder) { + int o = builder.endTable(); + return o; + } + + public static final class Vector extends BaseVector { + public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { __reset(_vector, _element_size, _bb); return this; } + + public FTag get(int j) { return get(new FTag(), j); } + public FTag get(FTag obj, int j) { return obj.__assign(__indirect(__element(j), bb), bb); } + } +} + diff --git a/core/src/main/flatbuffers/metric.fbs b/core/src/main/flatbuffers/metric.fbs new file mode 100644 index 00000000000..e38ab9c4c1b --- /dev/null +++ b/core/src/main/flatbuffers/metric.fbs @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +namespace org.apache.accumulo.core.metrics.flatbuffers; + +table FTag { + key:string; + value:string; +} + +table FMetric { + name:string; + type:string; + tags:[FTag]; + dvalue:double; + ivalue:int; + lvalue:long; +} + +root_type FMetric; \ No newline at end of file diff --git a/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java b/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java index 18ad9258828..92173adecf3 100644 --- a/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java +++ b/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java @@ -31,6 +31,12 @@ public interface MetricsInfo { + public static final String INSTANCE_NAME_TAG_KEY = "instance.name"; + public static final String PROCESS_NAME_TAG_KEY = "process.name"; + public static final String RESOURCE_GROUP_TAG_KEY = "resource.group"; + public static final String HOST_TAG_KEY = "host"; + public static final String PORT_TAG_KEY = "port"; + /** * Convenience method to create tag name / value pair for the instance name * @@ -39,7 +45,7 @@ public interface MetricsInfo { static Tag instanceNameTag(final String instanceName) { Objects.requireNonNull(instanceName, "cannot create the tag without providing the instance name"); - return Tag.of("instance.name", instanceName); + return Tag.of(INSTANCE_NAME_TAG_KEY, instanceName); } /** @@ -49,9 +55,9 @@ static Tag instanceNameTag(final String instanceName) { */ static Tag processTag(final String processName) { Objects.requireNonNull(processName, "cannot create the tag without providing the process name"); - return Tag.of("process.name", processName); + return Tag.of(PROCESS_NAME_TAG_KEY, processName); } - + /** * Convenience method to create tag name / value pair for the resource group name * @@ -59,9 +65,9 @@ static Tag processTag(final String processName) { */ static Tag resourceGroupTag(final String resourceGroupName) { if (resourceGroupName == null || resourceGroupName.isEmpty()) { - return Tag.of("resource.group", "NOT_PROVIDED"); + return Tag.of(RESOURCE_GROUP_TAG_KEY, "NOT_PROVIDED"); } - return Tag.of("resource.group", resourceGroupName); + return Tag.of(RESOURCE_GROUP_TAG_KEY, resourceGroupName); } /** @@ -73,10 +79,10 @@ static Tag resourceGroupTag(final String resourceGroupName) { static List addressTags(final HostAndPort hostAndPort) { Objects.requireNonNull(hostAndPort, "cannot create the tag without providing the hostAndPort"); List tags = new ArrayList<>(2); - tags.add(Tag.of("host", hostAndPort.getHost())); + tags.add(Tag.of(HOST_TAG_KEY, hostAndPort.getHost())); int port = hostAndPort.getPort(); if (port != 0) { - tags.add(Tag.of("port", Integer.toString(hostAndPort.getPort()))); + tags.add(Tag.of(PORT_TAG_KEY, Integer.toString(hostAndPort.getPort()))); } return Collections.unmodifiableList(tags); } diff --git a/core/src/main/java/org/apache/accumulo/core/rpc/clients/MetricsThriftClient.java b/core/src/main/java/org/apache/accumulo/core/rpc/clients/MetricsThriftClient.java new file mode 100644 index 00000000000..c6f37de2ddb --- /dev/null +++ b/core/src/main/java/org/apache/accumulo/core/rpc/clients/MetricsThriftClient.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.core.rpc.clients; + +import org.apache.accumulo.core.metrics.thrift.MetricService.Client; + +public class MetricsThriftClient extends ThriftClientTypes { + + protected MetricsThriftClient(String serviceName) { + super(serviceName, new Client.Factory()); + } + +} diff --git a/core/src/main/java/org/apache/accumulo/core/rpc/clients/ThriftClientTypes.java b/core/src/main/java/org/apache/accumulo/core/rpc/clients/ThriftClientTypes.java index d7f34c4846b..93c971023a5 100644 --- a/core/src/main/java/org/apache/accumulo/core/rpc/clients/ThriftClientTypes.java +++ b/core/src/main/java/org/apache/accumulo/core/rpc/clients/ThriftClientTypes.java @@ -46,6 +46,8 @@ public abstract class ThriftClientTypes { public static final ManagerThriftClient MANAGER = new ManagerThriftClient("mgr"); + public static final MetricsThriftClient METRICS = new MetricsThriftClient("metrics"); + public static final TabletServerThriftClient TABLET_SERVER = new TabletServerThriftClient("tserver"); diff --git a/core/src/main/scripts/generate-thrift.sh b/core/src/main/scripts/generate-thrift.sh index 0ad5911a33f..66e8eb632fe 100755 --- a/core/src/main/scripts/generate-thrift.sh +++ b/core/src/main/scripts/generate-thrift.sh @@ -32,7 +32,7 @@ [[ -z $REQUIRED_THRIFT_VERSION ]] && REQUIRED_THRIFT_VERSION='0.17.0' [[ -z $INCLUDED_MODULES ]] && INCLUDED_MODULES=() [[ -z $BASE_OUTPUT_PACKAGE ]] && BASE_OUTPUT_PACKAGE='org.apache.accumulo.core' -[[ -z $PACKAGES_TO_GENERATE ]] && PACKAGES_TO_GENERATE=(gc manager tabletserver securityImpl clientImpl dataImpl compaction tabletingest tablet tabletscan) +[[ -z $PACKAGES_TO_GENERATE ]] && PACKAGES_TO_GENERATE=(gc manager tabletserver securityImpl clientImpl dataImpl compaction tabletingest tablet tabletscan metrics) [[ -z $BUILD_DIR ]] && BUILD_DIR='target' [[ -z $LANGUAGES_TO_GENERATE ]] && LANGUAGES_TO_GENERATE=(java) [[ -z $FINAL_DIR ]] && FINAL_DIR='src/main' diff --git a/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricResponse.java b/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricResponse.java new file mode 100644 index 00000000000..0441c9ae168 --- /dev/null +++ b/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricResponse.java @@ -0,0 +1,887 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +/** + * Autogenerated by Thrift Compiler (0.17.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.accumulo.core.metrics.thrift; + +@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) +public class MetricResponse implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("MetricResponse"); + + private static final org.apache.thrift.protocol.TField SERVER_TYPE_FIELD_DESC = new org.apache.thrift.protocol.TField("serverType", org.apache.thrift.protocol.TType.I32, (short)1); + private static final org.apache.thrift.protocol.TField SERVER_FIELD_DESC = new org.apache.thrift.protocol.TField("server", org.apache.thrift.protocol.TType.STRING, (short)2); + private static final org.apache.thrift.protocol.TField RESOURCE_GROUP_FIELD_DESC = new org.apache.thrift.protocol.TField("resourceGroup", org.apache.thrift.protocol.TType.STRING, (short)3); + private static final org.apache.thrift.protocol.TField TIMESTAMP_FIELD_DESC = new org.apache.thrift.protocol.TField("timestamp", org.apache.thrift.protocol.TType.I64, (short)4); + private static final org.apache.thrift.protocol.TField METRICS_FIELD_DESC = new org.apache.thrift.protocol.TField("metrics", org.apache.thrift.protocol.TType.LIST, (short)5); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new MetricResponseStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new MetricResponseTupleSchemeFactory(); + + /** + * + * @see MetricSource + */ + public @org.apache.thrift.annotation.Nullable MetricSource serverType; // required + public @org.apache.thrift.annotation.Nullable java.lang.String server; // required + public @org.apache.thrift.annotation.Nullable java.lang.String resourceGroup; // required + public long timestamp; // required + public @org.apache.thrift.annotation.Nullable java.util.List metrics; // required + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + /** + * + * @see MetricSource + */ + SERVER_TYPE((short)1, "serverType"), + SERVER((short)2, "server"), + RESOURCE_GROUP((short)3, "resourceGroup"), + TIMESTAMP((short)4, "timestamp"), + METRICS((short)5, "metrics"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // SERVER_TYPE + return SERVER_TYPE; + case 2: // SERVER + return SERVER; + case 3: // RESOURCE_GROUP + return RESOURCE_GROUP; + case 4: // TIMESTAMP + return TIMESTAMP; + case 5: // METRICS + return METRICS; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + @Override + public short getThriftFieldId() { + return _thriftId; + } + + @Override + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + private static final int __TIMESTAMP_ISSET_ID = 0; + private byte __isset_bitfield = 0; + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.SERVER_TYPE, new org.apache.thrift.meta_data.FieldMetaData("serverType", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, MetricSource.class))); + tmpMap.put(_Fields.SERVER, new org.apache.thrift.meta_data.FieldMetaData("server", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); + tmpMap.put(_Fields.RESOURCE_GROUP, new org.apache.thrift.meta_data.FieldMetaData("resourceGroup", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); + tmpMap.put(_Fields.TIMESTAMP, new org.apache.thrift.meta_data.FieldMetaData("timestamp", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64))); + tmpMap.put(_Fields.METRICS, new org.apache.thrift.meta_data.FieldMetaData("metrics", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, + new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING , true)))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(MetricResponse.class, metaDataMap); + } + + public MetricResponse() { + } + + public MetricResponse( + MetricSource serverType, + java.lang.String server, + java.lang.String resourceGroup, + long timestamp, + java.util.List metrics) + { + this(); + this.serverType = serverType; + this.server = server; + this.resourceGroup = resourceGroup; + this.timestamp = timestamp; + setTimestampIsSet(true); + this.metrics = metrics; + } + + /** + * Performs a deep copy on other. + */ + public MetricResponse(MetricResponse other) { + __isset_bitfield = other.__isset_bitfield; + if (other.isSetServerType()) { + this.serverType = other.serverType; + } + if (other.isSetServer()) { + this.server = other.server; + } + if (other.isSetResourceGroup()) { + this.resourceGroup = other.resourceGroup; + } + this.timestamp = other.timestamp; + if (other.isSetMetrics()) { + java.util.List __this__metrics = new java.util.ArrayList(other.metrics); + this.metrics = __this__metrics; + } + } + + @Override + public MetricResponse deepCopy() { + return new MetricResponse(this); + } + + @Override + public void clear() { + this.serverType = null; + this.server = null; + this.resourceGroup = null; + setTimestampIsSet(false); + this.timestamp = 0; + this.metrics = null; + } + + /** + * + * @see MetricSource + */ + @org.apache.thrift.annotation.Nullable + public MetricSource getServerType() { + return this.serverType; + } + + /** + * + * @see MetricSource + */ + public MetricResponse setServerType(@org.apache.thrift.annotation.Nullable MetricSource serverType) { + this.serverType = serverType; + return this; + } + + public void unsetServerType() { + this.serverType = null; + } + + /** Returns true if field serverType is set (has been assigned a value) and false otherwise */ + public boolean isSetServerType() { + return this.serverType != null; + } + + public void setServerTypeIsSet(boolean value) { + if (!value) { + this.serverType = null; + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.String getServer() { + return this.server; + } + + public MetricResponse setServer(@org.apache.thrift.annotation.Nullable java.lang.String server) { + this.server = server; + return this; + } + + public void unsetServer() { + this.server = null; + } + + /** Returns true if field server is set (has been assigned a value) and false otherwise */ + public boolean isSetServer() { + return this.server != null; + } + + public void setServerIsSet(boolean value) { + if (!value) { + this.server = null; + } + } + + @org.apache.thrift.annotation.Nullable + public java.lang.String getResourceGroup() { + return this.resourceGroup; + } + + public MetricResponse setResourceGroup(@org.apache.thrift.annotation.Nullable java.lang.String resourceGroup) { + this.resourceGroup = resourceGroup; + return this; + } + + public void unsetResourceGroup() { + this.resourceGroup = null; + } + + /** Returns true if field resourceGroup is set (has been assigned a value) and false otherwise */ + public boolean isSetResourceGroup() { + return this.resourceGroup != null; + } + + public void setResourceGroupIsSet(boolean value) { + if (!value) { + this.resourceGroup = null; + } + } + + public long getTimestamp() { + return this.timestamp; + } + + public MetricResponse setTimestamp(long timestamp) { + this.timestamp = timestamp; + setTimestampIsSet(true); + return this; + } + + public void unsetTimestamp() { + __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __TIMESTAMP_ISSET_ID); + } + + /** Returns true if field timestamp is set (has been assigned a value) and false otherwise */ + public boolean isSetTimestamp() { + return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __TIMESTAMP_ISSET_ID); + } + + public void setTimestampIsSet(boolean value) { + __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __TIMESTAMP_ISSET_ID, value); + } + + public int getMetricsSize() { + return (this.metrics == null) ? 0 : this.metrics.size(); + } + + @org.apache.thrift.annotation.Nullable + public java.util.Iterator getMetricsIterator() { + return (this.metrics == null) ? null : this.metrics.iterator(); + } + + public void addToMetrics(java.nio.ByteBuffer elem) { + if (this.metrics == null) { + this.metrics = new java.util.ArrayList(); + } + this.metrics.add(elem); + } + + @org.apache.thrift.annotation.Nullable + public java.util.List getMetrics() { + return this.metrics; + } + + public MetricResponse setMetrics(@org.apache.thrift.annotation.Nullable java.util.List metrics) { + this.metrics = metrics; + return this; + } + + public void unsetMetrics() { + this.metrics = null; + } + + /** Returns true if field metrics is set (has been assigned a value) and false otherwise */ + public boolean isSetMetrics() { + return this.metrics != null; + } + + public void setMetricsIsSet(boolean value) { + if (!value) { + this.metrics = null; + } + } + + @Override + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case SERVER_TYPE: + if (value == null) { + unsetServerType(); + } else { + setServerType((MetricSource)value); + } + break; + + case SERVER: + if (value == null) { + unsetServer(); + } else { + setServer((java.lang.String)value); + } + break; + + case RESOURCE_GROUP: + if (value == null) { + unsetResourceGroup(); + } else { + setResourceGroup((java.lang.String)value); + } + break; + + case TIMESTAMP: + if (value == null) { + unsetTimestamp(); + } else { + setTimestamp((java.lang.Long)value); + } + break; + + case METRICS: + if (value == null) { + unsetMetrics(); + } else { + setMetrics((java.util.List)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + @Override + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case SERVER_TYPE: + return getServerType(); + + case SERVER: + return getServer(); + + case RESOURCE_GROUP: + return getResourceGroup(); + + case TIMESTAMP: + return getTimestamp(); + + case METRICS: + return getMetrics(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case SERVER_TYPE: + return isSetServerType(); + case SERVER: + return isSetServer(); + case RESOURCE_GROUP: + return isSetResourceGroup(); + case TIMESTAMP: + return isSetTimestamp(); + case METRICS: + return isSetMetrics(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof MetricResponse) + return this.equals((MetricResponse)that); + return false; + } + + public boolean equals(MetricResponse that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_serverType = true && this.isSetServerType(); + boolean that_present_serverType = true && that.isSetServerType(); + if (this_present_serverType || that_present_serverType) { + if (!(this_present_serverType && that_present_serverType)) + return false; + if (!this.serverType.equals(that.serverType)) + return false; + } + + boolean this_present_server = true && this.isSetServer(); + boolean that_present_server = true && that.isSetServer(); + if (this_present_server || that_present_server) { + if (!(this_present_server && that_present_server)) + return false; + if (!this.server.equals(that.server)) + return false; + } + + boolean this_present_resourceGroup = true && this.isSetResourceGroup(); + boolean that_present_resourceGroup = true && that.isSetResourceGroup(); + if (this_present_resourceGroup || that_present_resourceGroup) { + if (!(this_present_resourceGroup && that_present_resourceGroup)) + return false; + if (!this.resourceGroup.equals(that.resourceGroup)) + return false; + } + + boolean this_present_timestamp = true; + boolean that_present_timestamp = true; + if (this_present_timestamp || that_present_timestamp) { + if (!(this_present_timestamp && that_present_timestamp)) + return false; + if (this.timestamp != that.timestamp) + return false; + } + + boolean this_present_metrics = true && this.isSetMetrics(); + boolean that_present_metrics = true && that.isSetMetrics(); + if (this_present_metrics || that_present_metrics) { + if (!(this_present_metrics && that_present_metrics)) + return false; + if (!this.metrics.equals(that.metrics)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetServerType()) ? 131071 : 524287); + if (isSetServerType()) + hashCode = hashCode * 8191 + serverType.getValue(); + + hashCode = hashCode * 8191 + ((isSetServer()) ? 131071 : 524287); + if (isSetServer()) + hashCode = hashCode * 8191 + server.hashCode(); + + hashCode = hashCode * 8191 + ((isSetResourceGroup()) ? 131071 : 524287); + if (isSetResourceGroup()) + hashCode = hashCode * 8191 + resourceGroup.hashCode(); + + hashCode = hashCode * 8191 + org.apache.thrift.TBaseHelper.hashCode(timestamp); + + hashCode = hashCode * 8191 + ((isSetMetrics()) ? 131071 : 524287); + if (isSetMetrics()) + hashCode = hashCode * 8191 + metrics.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(MetricResponse other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetServerType(), other.isSetServerType()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetServerType()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.serverType, other.serverType); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetServer(), other.isSetServer()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetServer()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.server, other.server); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetResourceGroup(), other.isSetResourceGroup()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetResourceGroup()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.resourceGroup, other.resourceGroup); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetTimestamp(), other.isSetTimestamp()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetTimestamp()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.timestamp, other.timestamp); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetMetrics(), other.isSetMetrics()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetMetrics()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.metrics, other.metrics); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + @Override + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("MetricResponse("); + boolean first = true; + + sb.append("serverType:"); + if (this.serverType == null) { + sb.append("null"); + } else { + sb.append(this.serverType); + } + first = false; + if (!first) sb.append(", "); + sb.append("server:"); + if (this.server == null) { + sb.append("null"); + } else { + sb.append(this.server); + } + first = false; + if (!first) sb.append(", "); + sb.append("resourceGroup:"); + if (this.resourceGroup == null) { + sb.append("null"); + } else { + sb.append(this.resourceGroup); + } + first = false; + if (!first) sb.append(", "); + sb.append("timestamp:"); + sb.append(this.timestamp); + first = false; + if (!first) sb.append(", "); + sb.append("metrics:"); + if (this.metrics == null) { + sb.append("null"); + } else { + org.apache.thrift.TBaseHelper.toString(this.metrics, sb); + } + first = false; + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + // check for sub-struct validity + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor. + __isset_bitfield = 0; + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class MetricResponseStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public MetricResponseStandardScheme getScheme() { + return new MetricResponseStandardScheme(); + } + } + + private static class MetricResponseStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot, MetricResponse struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // SERVER_TYPE + if (schemeField.type == org.apache.thrift.protocol.TType.I32) { + struct.serverType = org.apache.accumulo.core.metrics.thrift.MetricSource.findByValue(iprot.readI32()); + struct.setServerTypeIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // SERVER + if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { + struct.server = iprot.readString(); + struct.setServerIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 3: // RESOURCE_GROUP + if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { + struct.resourceGroup = iprot.readString(); + struct.setResourceGroupIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 4: // TIMESTAMP + if (schemeField.type == org.apache.thrift.protocol.TType.I64) { + struct.timestamp = iprot.readI64(); + struct.setTimestampIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 5: // METRICS + if (schemeField.type == org.apache.thrift.protocol.TType.LIST) { + { + org.apache.thrift.protocol.TList _list0 = iprot.readListBegin(); + struct.metrics = new java.util.ArrayList(_list0.size); + @org.apache.thrift.annotation.Nullable java.nio.ByteBuffer _elem1; + for (int _i2 = 0; _i2 < _list0.size; ++_i2) + { + _elem1 = iprot.readBinary(); + struct.metrics.add(_elem1); + } + iprot.readListEnd(); + } + struct.setMetricsIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot, MetricResponse struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.serverType != null) { + oprot.writeFieldBegin(SERVER_TYPE_FIELD_DESC); + oprot.writeI32(struct.serverType.getValue()); + oprot.writeFieldEnd(); + } + if (struct.server != null) { + oprot.writeFieldBegin(SERVER_FIELD_DESC); + oprot.writeString(struct.server); + oprot.writeFieldEnd(); + } + if (struct.resourceGroup != null) { + oprot.writeFieldBegin(RESOURCE_GROUP_FIELD_DESC); + oprot.writeString(struct.resourceGroup); + oprot.writeFieldEnd(); + } + oprot.writeFieldBegin(TIMESTAMP_FIELD_DESC); + oprot.writeI64(struct.timestamp); + oprot.writeFieldEnd(); + if (struct.metrics != null) { + oprot.writeFieldBegin(METRICS_FIELD_DESC); + { + oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING, struct.metrics.size())); + for (java.nio.ByteBuffer _iter3 : struct.metrics) + { + oprot.writeBinary(_iter3); + } + oprot.writeListEnd(); + } + oprot.writeFieldEnd(); + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class MetricResponseTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public MetricResponseTupleScheme getScheme() { + return new MetricResponseTupleScheme(); + } + } + + private static class MetricResponseTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, MetricResponse struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetServerType()) { + optionals.set(0); + } + if (struct.isSetServer()) { + optionals.set(1); + } + if (struct.isSetResourceGroup()) { + optionals.set(2); + } + if (struct.isSetTimestamp()) { + optionals.set(3); + } + if (struct.isSetMetrics()) { + optionals.set(4); + } + oprot.writeBitSet(optionals, 5); + if (struct.isSetServerType()) { + oprot.writeI32(struct.serverType.getValue()); + } + if (struct.isSetServer()) { + oprot.writeString(struct.server); + } + if (struct.isSetResourceGroup()) { + oprot.writeString(struct.resourceGroup); + } + if (struct.isSetTimestamp()) { + oprot.writeI64(struct.timestamp); + } + if (struct.isSetMetrics()) { + { + oprot.writeI32(struct.metrics.size()); + for (java.nio.ByteBuffer _iter4 : struct.metrics) + { + oprot.writeBinary(_iter4); + } + } + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, MetricResponse struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + java.util.BitSet incoming = iprot.readBitSet(5); + if (incoming.get(0)) { + struct.serverType = org.apache.accumulo.core.metrics.thrift.MetricSource.findByValue(iprot.readI32()); + struct.setServerTypeIsSet(true); + } + if (incoming.get(1)) { + struct.server = iprot.readString(); + struct.setServerIsSet(true); + } + if (incoming.get(2)) { + struct.resourceGroup = iprot.readString(); + struct.setResourceGroupIsSet(true); + } + if (incoming.get(3)) { + struct.timestamp = iprot.readI64(); + struct.setTimestampIsSet(true); + } + if (incoming.get(4)) { + { + org.apache.thrift.protocol.TList _list5 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRING); + struct.metrics = new java.util.ArrayList(_list5.size); + @org.apache.thrift.annotation.Nullable java.nio.ByteBuffer _elem6; + for (int _i7 = 0; _i7 < _list5.size; ++_i7) + { + _elem6 = iprot.readBinary(); + struct.metrics.add(_elem6); + } + } + struct.setMetricsIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } + private static void unusedMethod() {} +} + diff --git a/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricService.java b/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricService.java new file mode 100644 index 00000000000..f48bd082239 --- /dev/null +++ b/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricService.java @@ -0,0 +1,1273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +/** + * Autogenerated by Thrift Compiler (0.17.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.accumulo.core.metrics.thrift; + +@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) +public class MetricService { + + public interface Iface { + + public MetricResponse getMetrics(org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo, org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials) throws org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException, org.apache.thrift.TException; + + } + + public interface AsyncIface { + + public void getMetrics(org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo, org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials, org.apache.thrift.async.AsyncMethodCallback resultHandler) throws org.apache.thrift.TException; + + } + + public static class Client extends org.apache.thrift.TServiceClient implements Iface { + public static class Factory implements org.apache.thrift.TServiceClientFactory { + public Factory() {} + @Override + public Client getClient(org.apache.thrift.protocol.TProtocol prot) { + return new Client(prot); + } + @Override + public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) { + return new Client(iprot, oprot); + } + } + + public Client(org.apache.thrift.protocol.TProtocol prot) + { + super(prot, prot); + } + + public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) { + super(iprot, oprot); + } + + @Override + public MetricResponse getMetrics(org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo, org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials) throws org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException, org.apache.thrift.TException + { + send_getMetrics(tinfo, credentials); + return recv_getMetrics(); + } + + public void send_getMetrics(org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo, org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials) throws org.apache.thrift.TException + { + getMetrics_args args = new getMetrics_args(); + args.setTinfo(tinfo); + args.setCredentials(credentials); + sendBase("getMetrics", args); + } + + public MetricResponse recv_getMetrics() throws org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException, org.apache.thrift.TException + { + getMetrics_result result = new getMetrics_result(); + receiveBase(result, "getMetrics"); + if (result.isSetSuccess()) { + return result.success; + } + if (result.sec != null) { + throw result.sec; + } + throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "getMetrics failed: unknown result"); + } + + } + public static class AsyncClient extends org.apache.thrift.async.TAsyncClient implements AsyncIface { + public static class Factory implements org.apache.thrift.async.TAsyncClientFactory { + private org.apache.thrift.async.TAsyncClientManager clientManager; + private org.apache.thrift.protocol.TProtocolFactory protocolFactory; + public Factory(org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.protocol.TProtocolFactory protocolFactory) { + this.clientManager = clientManager; + this.protocolFactory = protocolFactory; + } + @Override + public AsyncClient getAsyncClient(org.apache.thrift.transport.TNonblockingTransport transport) { + return new AsyncClient(protocolFactory, clientManager, transport); + } + } + + public AsyncClient(org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.transport.TNonblockingTransport transport) { + super(protocolFactory, clientManager, transport); + } + + @Override + public void getMetrics(org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo, org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials, org.apache.thrift.async.AsyncMethodCallback resultHandler) throws org.apache.thrift.TException { + checkReady(); + getMetrics_call method_call = new getMetrics_call(tinfo, credentials, resultHandler, this, ___protocolFactory, ___transport); + this.___currentMethod = method_call; + ___manager.call(method_call); + } + + public static class getMetrics_call extends org.apache.thrift.async.TAsyncMethodCall { + private org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo; + private org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials; + public getMetrics_call(org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo, org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials, org.apache.thrift.async.AsyncMethodCallback resultHandler, org.apache.thrift.async.TAsyncClient client, org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.transport.TNonblockingTransport transport) throws org.apache.thrift.TException { + super(client, protocolFactory, transport, resultHandler, false); + this.tinfo = tinfo; + this.credentials = credentials; + } + + @Override + public void write_args(org.apache.thrift.protocol.TProtocol prot) throws org.apache.thrift.TException { + prot.writeMessageBegin(new org.apache.thrift.protocol.TMessage("getMetrics", org.apache.thrift.protocol.TMessageType.CALL, 0)); + getMetrics_args args = new getMetrics_args(); + args.setTinfo(tinfo); + args.setCredentials(credentials); + args.write(prot); + prot.writeMessageEnd(); + } + + @Override + public MetricResponse getResult() throws org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException, org.apache.thrift.TException { + if (getState() != org.apache.thrift.async.TAsyncMethodCall.State.RESPONSE_READ) { + throw new java.lang.IllegalStateException("Method call not finished!"); + } + org.apache.thrift.transport.TMemoryInputTransport memoryTransport = new org.apache.thrift.transport.TMemoryInputTransport(getFrameBuffer().array()); + org.apache.thrift.protocol.TProtocol prot = client.getProtocolFactory().getProtocol(memoryTransport); + return (new Client(prot)).recv_getMetrics(); + } + } + + } + + public static class Processor extends org.apache.thrift.TBaseProcessor implements org.apache.thrift.TProcessor { + private static final org.slf4j.Logger _LOGGER = org.slf4j.LoggerFactory.getLogger(Processor.class.getName()); + public Processor(I iface) { + super(iface, getProcessMap(new java.util.HashMap>())); + } + + protected Processor(I iface, java.util.Map> processMap) { + super(iface, getProcessMap(processMap)); + } + + private static java.util.Map> getProcessMap(java.util.Map> processMap) { + processMap.put("getMetrics", new getMetrics()); + return processMap; + } + + public static class getMetrics extends org.apache.thrift.ProcessFunction { + public getMetrics() { + super("getMetrics"); + } + + @Override + public getMetrics_args getEmptyArgsInstance() { + return new getMetrics_args(); + } + + @Override + protected boolean isOneway() { + return false; + } + + @Override + protected boolean rethrowUnhandledExceptions() { + return false; + } + + @Override + public getMetrics_result getResult(I iface, getMetrics_args args) throws org.apache.thrift.TException { + getMetrics_result result = new getMetrics_result(); + try { + result.success = iface.getMetrics(args.tinfo, args.credentials); + } catch (org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException sec) { + result.sec = sec; + } + return result; + } + } + + } + + public static class AsyncProcessor extends org.apache.thrift.TBaseAsyncProcessor { + private static final org.slf4j.Logger _LOGGER = org.slf4j.LoggerFactory.getLogger(AsyncProcessor.class.getName()); + public AsyncProcessor(I iface) { + super(iface, getProcessMap(new java.util.HashMap>())); + } + + protected AsyncProcessor(I iface, java.util.Map> processMap) { + super(iface, getProcessMap(processMap)); + } + + private static java.util.Map> getProcessMap(java.util.Map> processMap) { + processMap.put("getMetrics", new getMetrics()); + return processMap; + } + + public static class getMetrics extends org.apache.thrift.AsyncProcessFunction { + public getMetrics() { + super("getMetrics"); + } + + @Override + public getMetrics_args getEmptyArgsInstance() { + return new getMetrics_args(); + } + + @Override + public org.apache.thrift.async.AsyncMethodCallback getResultHandler(final org.apache.thrift.server.AbstractNonblockingServer.AsyncFrameBuffer fb, final int seqid) { + final org.apache.thrift.AsyncProcessFunction fcall = this; + return new org.apache.thrift.async.AsyncMethodCallback() { + @Override + public void onComplete(MetricResponse o) { + getMetrics_result result = new getMetrics_result(); + result.success = o; + try { + fcall.sendResponse(fb, result, org.apache.thrift.protocol.TMessageType.REPLY,seqid); + } catch (org.apache.thrift.transport.TTransportException e) { + _LOGGER.error("TTransportException writing to internal frame buffer", e); + fb.close(); + } catch (java.lang.Exception e) { + _LOGGER.error("Exception writing to internal frame buffer", e); + onError(e); + } + } + @Override + public void onError(java.lang.Exception e) { + byte msgType = org.apache.thrift.protocol.TMessageType.REPLY; + org.apache.thrift.TSerializable msg; + getMetrics_result result = new getMetrics_result(); + if (e instanceof org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException) { + result.sec = (org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException) e; + result.setSecIsSet(true); + msg = result; + } else if (e instanceof org.apache.thrift.transport.TTransportException) { + _LOGGER.error("TTransportException inside handler", e); + fb.close(); + return; + } else if (e instanceof org.apache.thrift.TApplicationException) { + _LOGGER.error("TApplicationException inside handler", e); + msgType = org.apache.thrift.protocol.TMessageType.EXCEPTION; + msg = (org.apache.thrift.TApplicationException)e; + } else { + _LOGGER.error("Exception inside handler", e); + msgType = org.apache.thrift.protocol.TMessageType.EXCEPTION; + msg = new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.INTERNAL_ERROR, e.getMessage()); + } + try { + fcall.sendResponse(fb,msg,msgType,seqid); + } catch (java.lang.Exception ex) { + _LOGGER.error("Exception writing to internal frame buffer", ex); + fb.close(); + } + } + }; + } + + @Override + protected boolean isOneway() { + return false; + } + + @Override + public void start(I iface, getMetrics_args args, org.apache.thrift.async.AsyncMethodCallback resultHandler) throws org.apache.thrift.TException { + iface.getMetrics(args.tinfo, args.credentials,resultHandler); + } + } + + } + + @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) + public static class getMetrics_args implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("getMetrics_args"); + + private static final org.apache.thrift.protocol.TField TINFO_FIELD_DESC = new org.apache.thrift.protocol.TField("tinfo", org.apache.thrift.protocol.TType.STRUCT, (short)1); + private static final org.apache.thrift.protocol.TField CREDENTIALS_FIELD_DESC = new org.apache.thrift.protocol.TField("credentials", org.apache.thrift.protocol.TType.STRUCT, (short)2); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new getMetrics_argsStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new getMetrics_argsTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo; // required + public @org.apache.thrift.annotation.Nullable org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials; // required + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + TINFO((short)1, "tinfo"), + CREDENTIALS((short)2, "credentials"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 1: // TINFO + return TINFO; + case 2: // CREDENTIALS + return CREDENTIALS; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + @Override + public short getThriftFieldId() { + return _thriftId; + } + + @Override + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.TINFO, new org.apache.thrift.meta_data.FieldMetaData("tinfo", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, org.apache.accumulo.core.clientImpl.thrift.TInfo.class))); + tmpMap.put(_Fields.CREDENTIALS, new org.apache.thrift.meta_data.FieldMetaData("credentials", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, org.apache.accumulo.core.securityImpl.thrift.TCredentials.class))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(getMetrics_args.class, metaDataMap); + } + + public getMetrics_args() { + } + + public getMetrics_args( + org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo, + org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials) + { + this(); + this.tinfo = tinfo; + this.credentials = credentials; + } + + /** + * Performs a deep copy on other. + */ + public getMetrics_args(getMetrics_args other) { + if (other.isSetTinfo()) { + this.tinfo = new org.apache.accumulo.core.clientImpl.thrift.TInfo(other.tinfo); + } + if (other.isSetCredentials()) { + this.credentials = new org.apache.accumulo.core.securityImpl.thrift.TCredentials(other.credentials); + } + } + + @Override + public getMetrics_args deepCopy() { + return new getMetrics_args(this); + } + + @Override + public void clear() { + this.tinfo = null; + this.credentials = null; + } + + @org.apache.thrift.annotation.Nullable + public org.apache.accumulo.core.clientImpl.thrift.TInfo getTinfo() { + return this.tinfo; + } + + public getMetrics_args setTinfo(@org.apache.thrift.annotation.Nullable org.apache.accumulo.core.clientImpl.thrift.TInfo tinfo) { + this.tinfo = tinfo; + return this; + } + + public void unsetTinfo() { + this.tinfo = null; + } + + /** Returns true if field tinfo is set (has been assigned a value) and false otherwise */ + public boolean isSetTinfo() { + return this.tinfo != null; + } + + public void setTinfoIsSet(boolean value) { + if (!value) { + this.tinfo = null; + } + } + + @org.apache.thrift.annotation.Nullable + public org.apache.accumulo.core.securityImpl.thrift.TCredentials getCredentials() { + return this.credentials; + } + + public getMetrics_args setCredentials(@org.apache.thrift.annotation.Nullable org.apache.accumulo.core.securityImpl.thrift.TCredentials credentials) { + this.credentials = credentials; + return this; + } + + public void unsetCredentials() { + this.credentials = null; + } + + /** Returns true if field credentials is set (has been assigned a value) and false otherwise */ + public boolean isSetCredentials() { + return this.credentials != null; + } + + public void setCredentialsIsSet(boolean value) { + if (!value) { + this.credentials = null; + } + } + + @Override + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case TINFO: + if (value == null) { + unsetTinfo(); + } else { + setTinfo((org.apache.accumulo.core.clientImpl.thrift.TInfo)value); + } + break; + + case CREDENTIALS: + if (value == null) { + unsetCredentials(); + } else { + setCredentials((org.apache.accumulo.core.securityImpl.thrift.TCredentials)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + @Override + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case TINFO: + return getTinfo(); + + case CREDENTIALS: + return getCredentials(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case TINFO: + return isSetTinfo(); + case CREDENTIALS: + return isSetCredentials(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof getMetrics_args) + return this.equals((getMetrics_args)that); + return false; + } + + public boolean equals(getMetrics_args that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_tinfo = true && this.isSetTinfo(); + boolean that_present_tinfo = true && that.isSetTinfo(); + if (this_present_tinfo || that_present_tinfo) { + if (!(this_present_tinfo && that_present_tinfo)) + return false; + if (!this.tinfo.equals(that.tinfo)) + return false; + } + + boolean this_present_credentials = true && this.isSetCredentials(); + boolean that_present_credentials = true && that.isSetCredentials(); + if (this_present_credentials || that_present_credentials) { + if (!(this_present_credentials && that_present_credentials)) + return false; + if (!this.credentials.equals(that.credentials)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetTinfo()) ? 131071 : 524287); + if (isSetTinfo()) + hashCode = hashCode * 8191 + tinfo.hashCode(); + + hashCode = hashCode * 8191 + ((isSetCredentials()) ? 131071 : 524287); + if (isSetCredentials()) + hashCode = hashCode * 8191 + credentials.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(getMetrics_args other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetTinfo(), other.isSetTinfo()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetTinfo()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.tinfo, other.tinfo); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetCredentials(), other.isSetCredentials()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetCredentials()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.credentials, other.credentials); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + @Override + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("getMetrics_args("); + boolean first = true; + + sb.append("tinfo:"); + if (this.tinfo == null) { + sb.append("null"); + } else { + sb.append(this.tinfo); + } + first = false; + if (!first) sb.append(", "); + sb.append("credentials:"); + if (this.credentials == null) { + sb.append("null"); + } else { + sb.append(this.credentials); + } + first = false; + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + // check for sub-struct validity + if (tinfo != null) { + tinfo.validate(); + } + if (credentials != null) { + credentials.validate(); + } + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class getMetrics_argsStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public getMetrics_argsStandardScheme getScheme() { + return new getMetrics_argsStandardScheme(); + } + } + + private static class getMetrics_argsStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot, getMetrics_args struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 1: // TINFO + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.tinfo = new org.apache.accumulo.core.clientImpl.thrift.TInfo(); + struct.tinfo.read(iprot); + struct.setTinfoIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 2: // CREDENTIALS + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.credentials = new org.apache.accumulo.core.securityImpl.thrift.TCredentials(); + struct.credentials.read(iprot); + struct.setCredentialsIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot, getMetrics_args struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.tinfo != null) { + oprot.writeFieldBegin(TINFO_FIELD_DESC); + struct.tinfo.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.credentials != null) { + oprot.writeFieldBegin(CREDENTIALS_FIELD_DESC); + struct.credentials.write(oprot); + oprot.writeFieldEnd(); + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class getMetrics_argsTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public getMetrics_argsTupleScheme getScheme() { + return new getMetrics_argsTupleScheme(); + } + } + + private static class getMetrics_argsTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, getMetrics_args struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetTinfo()) { + optionals.set(0); + } + if (struct.isSetCredentials()) { + optionals.set(1); + } + oprot.writeBitSet(optionals, 2); + if (struct.isSetTinfo()) { + struct.tinfo.write(oprot); + } + if (struct.isSetCredentials()) { + struct.credentials.write(oprot); + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, getMetrics_args struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + java.util.BitSet incoming = iprot.readBitSet(2); + if (incoming.get(0)) { + struct.tinfo = new org.apache.accumulo.core.clientImpl.thrift.TInfo(); + struct.tinfo.read(iprot); + struct.setTinfoIsSet(true); + } + if (incoming.get(1)) { + struct.credentials = new org.apache.accumulo.core.securityImpl.thrift.TCredentials(); + struct.credentials.read(iprot); + struct.setCredentialsIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } + } + + @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) + public static class getMetrics_result implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("getMetrics_result"); + + private static final org.apache.thrift.protocol.TField SUCCESS_FIELD_DESC = new org.apache.thrift.protocol.TField("success", org.apache.thrift.protocol.TType.STRUCT, (short)0); + private static final org.apache.thrift.protocol.TField SEC_FIELD_DESC = new org.apache.thrift.protocol.TField("sec", org.apache.thrift.protocol.TType.STRUCT, (short)1); + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new getMetrics_resultStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new getMetrics_resultTupleSchemeFactory(); + + public @org.apache.thrift.annotation.Nullable MetricResponse success; // required + public @org.apache.thrift.annotation.Nullable org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException sec; // required + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { + SUCCESS((short)0, "success"), + SEC((short)1, "sec"); + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + case 0: // SUCCESS + return SUCCESS; + case 1: // SEC + return SEC; + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + @org.apache.thrift.annotation.Nullable + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + @Override + public short getThriftFieldId() { + return _thriftId; + } + + @Override + public java.lang.String getFieldName() { + return _fieldName; + } + } + + // isset id assignments + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + tmpMap.put(_Fields.SUCCESS, new org.apache.thrift.meta_data.FieldMetaData("success", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, MetricResponse.class))); + tmpMap.put(_Fields.SEC, new org.apache.thrift.meta_data.FieldMetaData("sec", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException.class))); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(getMetrics_result.class, metaDataMap); + } + + public getMetrics_result() { + } + + public getMetrics_result( + MetricResponse success, + org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException sec) + { + this(); + this.success = success; + this.sec = sec; + } + + /** + * Performs a deep copy on other. + */ + public getMetrics_result(getMetrics_result other) { + if (other.isSetSuccess()) { + this.success = new MetricResponse(other.success); + } + if (other.isSetSec()) { + this.sec = new org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException(other.sec); + } + } + + @Override + public getMetrics_result deepCopy() { + return new getMetrics_result(this); + } + + @Override + public void clear() { + this.success = null; + this.sec = null; + } + + @org.apache.thrift.annotation.Nullable + public MetricResponse getSuccess() { + return this.success; + } + + public getMetrics_result setSuccess(@org.apache.thrift.annotation.Nullable MetricResponse success) { + this.success = success; + return this; + } + + public void unsetSuccess() { + this.success = null; + } + + /** Returns true if field success is set (has been assigned a value) and false otherwise */ + public boolean isSetSuccess() { + return this.success != null; + } + + public void setSuccessIsSet(boolean value) { + if (!value) { + this.success = null; + } + } + + @org.apache.thrift.annotation.Nullable + public org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException getSec() { + return this.sec; + } + + public getMetrics_result setSec(@org.apache.thrift.annotation.Nullable org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException sec) { + this.sec = sec; + return this; + } + + public void unsetSec() { + this.sec = null; + } + + /** Returns true if field sec is set (has been assigned a value) and false otherwise */ + public boolean isSetSec() { + return this.sec != null; + } + + public void setSecIsSet(boolean value) { + if (!value) { + this.sec = null; + } + } + + @Override + public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable java.lang.Object value) { + switch (field) { + case SUCCESS: + if (value == null) { + unsetSuccess(); + } else { + setSuccess((MetricResponse)value); + } + break; + + case SEC: + if (value == null) { + unsetSec(); + } else { + setSec((org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException)value); + } + break; + + } + } + + @org.apache.thrift.annotation.Nullable + @Override + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + case SUCCESS: + return getSuccess(); + + case SEC: + return getSec(); + + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + @Override + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + case SUCCESS: + return isSetSuccess(); + case SEC: + return isSetSec(); + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that instanceof getMetrics_result) + return this.equals((getMetrics_result)that); + return false; + } + + public boolean equals(getMetrics_result that) { + if (that == null) + return false; + if (this == that) + return true; + + boolean this_present_success = true && this.isSetSuccess(); + boolean that_present_success = true && that.isSetSuccess(); + if (this_present_success || that_present_success) { + if (!(this_present_success && that_present_success)) + return false; + if (!this.success.equals(that.success)) + return false; + } + + boolean this_present_sec = true && this.isSetSec(); + boolean that_present_sec = true && that.isSetSec(); + if (this_present_sec || that_present_sec) { + if (!(this_present_sec && that_present_sec)) + return false; + if (!this.sec.equals(that.sec)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 8191 + ((isSetSuccess()) ? 131071 : 524287); + if (isSetSuccess()) + hashCode = hashCode * 8191 + success.hashCode(); + + hashCode = hashCode * 8191 + ((isSetSec()) ? 131071 : 524287); + if (isSetSec()) + hashCode = hashCode * 8191 + sec.hashCode(); + + return hashCode; + } + + @Override + public int compareTo(getMetrics_result other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + lastComparison = java.lang.Boolean.compare(isSetSuccess(), other.isSetSuccess()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetSuccess()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.success, other.success); + if (lastComparison != 0) { + return lastComparison; + } + } + lastComparison = java.lang.Boolean.compare(isSetSec(), other.isSetSec()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetSec()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.sec, other.sec); + if (lastComparison != 0) { + return lastComparison; + } + } + return 0; + } + + @org.apache.thrift.annotation.Nullable + @Override + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("getMetrics_result("); + boolean first = true; + + sb.append("success:"); + if (this.success == null) { + sb.append("null"); + } else { + sb.append(this.success); + } + first = false; + if (!first) sb.append(", "); + sb.append("sec:"); + if (this.sec == null) { + sb.append("null"); + } else { + sb.append(this.sec); + } + first = false; + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + // check for sub-struct validity + if (success != null) { + success.validate(); + } + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class getMetrics_resultStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public getMetrics_resultStandardScheme getScheme() { + return new getMetrics_resultStandardScheme(); + } + } + + private static class getMetrics_resultStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + @Override + public void read(org.apache.thrift.protocol.TProtocol iprot, getMetrics_result struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + case 0: // SUCCESS + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.success = new MetricResponse(); + struct.success.read(iprot); + struct.setSuccessIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + case 1: // SEC + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.sec = new org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException(); + struct.sec.read(iprot); + struct.setSecIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + @Override + public void write(org.apache.thrift.protocol.TProtocol oprot, getMetrics_result struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + if (struct.success != null) { + oprot.writeFieldBegin(SUCCESS_FIELD_DESC); + struct.success.write(oprot); + oprot.writeFieldEnd(); + } + if (struct.sec != null) { + oprot.writeFieldBegin(SEC_FIELD_DESC); + struct.sec.write(oprot); + oprot.writeFieldEnd(); + } + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class getMetrics_resultTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + @Override + public getMetrics_resultTupleScheme getScheme() { + return new getMetrics_resultTupleScheme(); + } + } + + private static class getMetrics_resultTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, getMetrics_result struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + java.util.BitSet optionals = new java.util.BitSet(); + if (struct.isSetSuccess()) { + optionals.set(0); + } + if (struct.isSetSec()) { + optionals.set(1); + } + oprot.writeBitSet(optionals, 2); + if (struct.isSetSuccess()) { + struct.success.write(oprot); + } + if (struct.isSetSec()) { + struct.sec.write(oprot); + } + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, getMetrics_result struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + java.util.BitSet incoming = iprot.readBitSet(2); + if (incoming.get(0)) { + struct.success = new MetricResponse(); + struct.success.read(iprot); + struct.setSuccessIsSet(true); + } + if (incoming.get(1)) { + struct.sec = new org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException(); + struct.sec.read(iprot); + struct.setSecIsSet(true); + } + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } + } + + private static void unusedMethod() {} +} diff --git a/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricSource.java b/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricSource.java new file mode 100644 index 00000000000..3075ab8d0a5 --- /dev/null +++ b/core/src/main/thrift-gen-java/org/apache/accumulo/core/metrics/thrift/MetricSource.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +/** + * Autogenerated by Thrift Compiler (0.17.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package org.apache.accumulo.core.metrics.thrift; + + +public enum MetricSource implements org.apache.thrift.TEnum { + COMPACTOR(0), + GARBAGE_COLLECTOR(1), + MANAGER(2), + SCAN_SERVER(3), + TABLET_SERVER(4); + + private final int value; + + private MetricSource(int value) { + this.value = value; + } + + /** + * Get the integer value of this enum value, as defined in the Thrift IDL. + */ + @Override + public int getValue() { + return value; + } + + /** + * Find a the enum type by its integer value, as defined in the Thrift IDL. + * @return null if the value is not found. + */ + @org.apache.thrift.annotation.Nullable + public static MetricSource findByValue(int value) { + switch (value) { + case 0: + return COMPACTOR; + case 1: + return GARBAGE_COLLECTOR; + case 2: + return MANAGER; + case 3: + return SCAN_SERVER; + case 4: + return TABLET_SERVER; + default: + return null; + } + } +} diff --git a/core/src/main/thrift/metrics.thrift b/core/src/main/thrift/metrics.thrift new file mode 100644 index 00000000000..5a5a250767d --- /dev/null +++ b/core/src/main/thrift/metrics.thrift @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +namespace java org.apache.accumulo.core.metrics.thrift +namespace cpp org.apache.accumulo.core.metrics.thrift + +include "client.thrift" +include "security.thrift" + +enum MetricSource { + COMPACTOR + GARBAGE_COLLECTOR + MANAGER + SCAN_SERVER + TABLET_SERVER +} + +struct MetricResponse { + 1:MetricSource serverType + 2:string server + 3:string resourceGroup + 4:i64 timestamp + 5:list metrics +} + +service MetricService { + + MetricResponse getMetrics( + 1:client.TInfo tinfo + 2:security.TCredentials credentials + ) throws ( + 1:client.ThriftSecurityException sec + ) + +} diff --git a/pom.xml b/pom.xml index dedaefcc6d0..72cecc21c56 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,7 @@ 1.78.1 5.5.0 2.24.1 + 24.3.25 3.4.0 2.24.0 1.34.1 @@ -279,6 +280,11 @@ error_prone_annotations ${version.errorprone} + + com.google.flatbuffers + flatbuffers-java + ${version.flatbuffers} + com.google.guava @@ -1507,6 +1513,36 @@ + + add-flatbuffers-java-source + + + src/main/flatbuffers-gen-java + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-flatbuffers-java-source + + add-source + + generate-sources + + + src/main/flatbuffers-gen-java + + + + + + + + add-thrift-java-source diff --git a/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java b/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java index ba621f6b013..b0b1608dcc6 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java +++ b/server/base/src/main/java/org/apache/accumulo/server/AbstractServer.java @@ -31,10 +31,12 @@ import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.conf.SiteConfiguration; import org.apache.accumulo.core.metrics.MetricsProducer; +import org.apache.accumulo.core.metrics.thrift.MetricSource; import org.apache.accumulo.core.trace.TraceUtil; import org.apache.accumulo.core.util.Timer; import org.apache.accumulo.core.util.threads.ThreadPools; import org.apache.accumulo.server.mem.LowMemoryDetector; +import org.apache.accumulo.server.metrics.MetricServiceHandler; import org.apache.accumulo.server.metrics.ProcessMetrics; import org.apache.accumulo.server.security.SecurityUtil; import org.slf4j.Logger; @@ -165,6 +167,10 @@ public String getApplicationName() { return applicationName; } + protected MetricServiceHandler createMetricServiceHandler(MetricSource serverType) { + return new MetricServiceHandler(serverType, getResourceGroup(), context); + } + @Override public void close() {} diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java new file mode 100644 index 00000000000..0106058d719 --- /dev/null +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.server.metrics; + +import static org.apache.accumulo.core.metrics.MetricsInfo.INSTANCE_NAME_TAG_KEY; +import static org.apache.accumulo.core.metrics.MetricsInfo.PORT_TAG_KEY; +import static org.apache.accumulo.core.metrics.MetricsInfo.PROCESS_NAME_TAG_KEY; +import static org.apache.accumulo.core.metrics.MetricsInfo.RESOURCE_GROUP_TAG_KEY; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.accumulo.core.metrics.flatbuffers.FMetric; +import org.apache.accumulo.core.metrics.flatbuffers.FTag; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; + +import com.google.flatbuffers.FlatBufferBuilder; + +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.FunctionTimer; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.distribution.HistogramSnapshot; +import io.micrometer.core.instrument.distribution.ValueAtPercentile; + +public class MetricResponseWrapper extends MetricResponse { + + private static final long serialVersionUID = 1L; + + private final TimeUnit UNIT = TimeUnit.SECONDS; + private final FlatBufferBuilder builder = new FlatBufferBuilder(); + + private List reduceTags(List tags, List extraTags) { + return Stream.concat(tags.stream(), extraTags.stream()).filter(t -> { + return !t.getKey().equals(INSTANCE_NAME_TAG_KEY) && + !t.getKey().equals(PROCESS_NAME_TAG_KEY) && + !t.getKey().equals(RESOURCE_GROUP_TAG_KEY) && + !t.getKey().equals(RESOURCE_GROUP_TAG_KEY) && + !t.getKey().equals(PORT_TAG_KEY); + }).collect(Collectors.toList()); + } + + private void addMetric(Meter.Id id, List extraTags, int value) { + + final String type = id.getType().name(); + final String name = id.getName(); + final List tags = id.getTags(); + + builder.clear(); + FMetric.startFMetric(builder); + FMetric.addName(builder, builder.createString(name)); + FMetric.addType(builder, builder.createString(type)); + FMetric.addIvalue(builder, value); + List tagList = reduceTags(tags, extraTags); + int[] tagRefs = new int[tagList.size()]; + for (int idx = 0; idx < tagList.size(); idx++) { + Tag t = tagList.get(idx); + int k = builder.createString(t.getKey()); + int v = builder.createString(t.getValue()); + tagRefs[idx] = FTag.createFTag(builder, k, v); + } + FMetric.createTagsVector(builder, tagRefs); + int metricRef = FMetric.endFMetric(builder); + builder.finish(metricRef); + this.addToMetrics(builder.dataBuffer()); + } + + private void addMetric(Meter.Id id, List extraTags, long value) { + final String type = id.getType().name(); + final String name = id.getName(); + final List tags = id.getTags(); + + builder.clear(); + FMetric.startFMetric(builder); + FMetric.addName(builder, builder.createString(name)); + FMetric.addType(builder, builder.createString(type)); + FMetric.addLvalue(builder, value); + List tagList = reduceTags(tags, extraTags); + int[] tagRefs = new int[tagList.size()]; + for (int idx = 0; idx < tagList.size(); idx++) { + Tag t = tagList.get(idx); + int k = builder.createString(t.getKey()); + int v = builder.createString(t.getValue()); + tagRefs[idx] = FTag.createFTag(builder, k, v); + } + FMetric.createTagsVector(builder, tagRefs); + int metricRef = FMetric.endFMetric(builder); + builder.finish(metricRef); + this.addToMetrics(builder.dataBuffer()); + } + + private void addMetric(Meter.Id id, List extraTags, double value) { + final String type = id.getType().name(); + final String name = id.getName(); + final List tags = id.getTags(); + + builder.clear(); + FMetric.startFMetric(builder); + FMetric.addName(builder, builder.createString(name)); + FMetric.addType(builder, builder.createString(type)); + FMetric.addDvalue(builder, value); + List tagList = reduceTags(tags, extraTags); + int[] tagRefs = new int[tagList.size()]; + for (int idx = 0; idx < tagList.size(); idx++) { + Tag t = tagList.get(idx); + int k = builder.createString(t.getKey()); + int v = builder.createString(t.getValue()); + tagRefs[idx] = FTag.createFTag(builder, k, v); + } + FMetric.createTagsVector(builder, tagRefs); + int metricRef = FMetric.endFMetric(builder); + builder.finish(metricRef); + this.addToMetrics(builder.dataBuffer()); + } + + public Consumer writeMeter(Meter meter) { + for (Measurement m : meter.measure()) { + addMetric(meter.getId().withTag(m.getStatistic()), List.of(), m.getValue()); + } + return null; + } + + public Consumer writeFunctionTimer(FunctionTimer ft) { + addMetric(ft.getId(), List.of(Tag.of("statistic", "count")), ft.count()); + addMetric(ft.getId(), List.of(Tag.of("statistic", "average")), ft.mean(UNIT)); + addMetric(ft.getId(), List.of(Tag.of("statistic", "sum")), ft.totalTime(UNIT)); + return null; + } + + public Consumer writeTimer(Timer t) { + addMetric(t.getId(), List.of(Tag.of("statistic", "count")), t.count()); + addMetric(t.getId(), List.of(Tag.of("statistic", "avg")), t.mean(UNIT)); + addMetric(t.getId(), List.of(Tag.of("statistic", "max")), t.max(UNIT)); + addMetric(t.getId(), List.of(Tag.of("statistic", "sum")), t.totalTime(UNIT)); + return null; + } + + public Consumer writeLongTaskTimer(LongTaskTimer t) { + addMetric(t.getId(), List.of(Tag.of("statistic", "avg")), t.mean(UNIT)); + addMetric(t.getId(), List.of(Tag.of("statistic", "max")), t.max(UNIT)); + addMetric(t.getId(), List.of(Tag.of("statistic", "duration")), t.duration(UNIT)); + addMetric(t.getId(), List.of(Tag.of("statistic", "active")), t.activeTasks()); + return null; + } + + public Consumer writeDistributionSummary(DistributionSummary d) { + HistogramSnapshot h = d.takeSnapshot(); + ValueAtPercentile[] percentiles = h.percentileValues(); + for (ValueAtPercentile p : percentiles) { + addMetric(d.getId(), List.of(Tag.of("percentile", Double.toString(p.percentile()))), + p.value()); + } + addMetric(d.getId(), List.of(Tag.of("statistic", "count")), d.count()); + addMetric(d.getId(), List.of(Tag.of("statistic", "avg")), d.mean()); + addMetric(d.getId(), List.of(Tag.of("statistic", "max")), d.max()); + addMetric(d.getId(), List.of(Tag.of("statistic", "sum")), d.totalAmount()); + return null; + } + +} diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java new file mode 100644 index 00000000000..3531fbcf1d3 --- /dev/null +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.server.metrics; + +import java.util.List; + +import org.apache.accumulo.core.clientImpl.thrift.SecurityErrorCode; +import org.apache.accumulo.core.clientImpl.thrift.TInfo; +import org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.core.metrics.thrift.MetricService; +import org.apache.accumulo.core.metrics.thrift.MetricSource; +import org.apache.accumulo.core.securityImpl.thrift.TCredentials; +import org.apache.accumulo.server.ServerContext; +import org.apache.thrift.TException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.net.HostAndPort; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Metrics; + +public class MetricServiceHandler implements MetricService.Iface { + + private static final Logger LOG = LoggerFactory.getLogger(MetricServiceHandler.class); + + private final MetricSource type; + private final String resourceGroup; + private final ServerContext ctx; + + private String host; + + public MetricServiceHandler(MetricSource source, String resourceGroup, ServerContext ctx) { + this.type = source; + this.resourceGroup = resourceGroup; + this.ctx = ctx; + } + + public void setHost(HostAndPort host) { + this.host = host.toString(); + } + + @Override + public MetricResponse getMetrics(TInfo tinfo, TCredentials credentials) + throws ThriftSecurityException, TException { + + if (!(ctx.getSecurityOperation().isSystemUser(credentials) + && ctx.getSecurityOperation().authenticateUser(credentials, credentials))) { + throw new ThriftSecurityException(credentials.getPrincipal(), + SecurityErrorCode.PERMISSION_DENIED); + } + + final MetricResponseWrapper response = new MetricResponseWrapper(); + + if (host == null) { + LOG.error("Host is not set, this should have been done after starting the Thrift service."); + return response; + } + + if (!ctx.getMetricsInfo().isMetricsEnabled()) { + return response; + } + + response.setServerType(type); + response.setServer(host); + response.setResourceGroup(resourceGroup); + response.setTimestamp(System.currentTimeMillis()); + + final List meters = Metrics.globalRegistry.getMeters(); + for (Meter m : meters) { + if (m.getId().getName().startsWith("accumulo.")) { + m.use(response::writeMeter, response::writeMeter, response::writeTimer, + response::writeDistributionSummary, response::writeLongTaskTimer, response::writeMeter, + response::writeMeter, response::writeFunctionTimer, response::writeMeter); + } + } + + return response; + } + +} diff --git a/server/base/src/main/java/org/apache/accumulo/server/rpc/ThriftProcessorTypes.java b/server/base/src/main/java/org/apache/accumulo/server/rpc/ThriftProcessorTypes.java index 6f0a6086eba..7479448f7e8 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/rpc/ThriftProcessorTypes.java +++ b/server/base/src/main/java/org/apache/accumulo/server/rpc/ThriftProcessorTypes.java @@ -24,6 +24,7 @@ import org.apache.accumulo.core.gc.thrift.GCMonitorService; import org.apache.accumulo.core.manager.thrift.FateService; import org.apache.accumulo.core.manager.thrift.ManagerClientService; +import org.apache.accumulo.core.metrics.thrift.MetricService; import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; import org.apache.accumulo.core.tablet.thrift.TabletManagementClientService; import org.apache.accumulo.core.tabletingest.thrift.TabletIngestClientService; @@ -32,6 +33,7 @@ import org.apache.accumulo.core.trace.TraceUtil; import org.apache.accumulo.server.ServerContext; import org.apache.accumulo.server.client.ClientServiceHandler; +import org.apache.accumulo.server.metrics.MetricServiceHandler; import org.apache.thrift.TBaseProcessor; import org.apache.thrift.TMultiplexedProcessor; import org.apache.thrift.TProcessor; @@ -81,6 +83,9 @@ public > TProcessor getTProcessor( private static final ThriftProcessorTypes MANAGER = new ThriftProcessorTypes<>(ThriftClientTypes.MANAGER); + private static final ThriftProcessorTypes METRICS = + new ThriftProcessorTypes<>(ThriftClientTypes.METRICS); + @VisibleForTesting public static final ThriftProcessorTypes TABLET_SERVER = new ThriftProcessorTypes<>(ThriftClientTypes.TABLET_SERVER); @@ -96,26 +101,32 @@ public > TProcessor getTProcessor( new ThriftProcessorTypes<>(ThriftClientTypes.TABLET_MGMT); public static TMultiplexedProcessor getCompactorTProcessor(ClientServiceHandler clientHandler, - CompactorService.Iface serviceHandler, ServerContext context) { + CompactorService.Iface serviceHandler, MetricServiceHandler metricHandler, + ServerContext context) { TMultiplexedProcessor muxProcessor = new TMultiplexedProcessor(); muxProcessor.registerProcessor(CLIENT.getServiceName(), CLIENT.getTProcessor( ClientService.Processor.class, ClientService.Iface.class, clientHandler, context)); muxProcessor.registerProcessor(COMPACTOR.getServiceName(), COMPACTOR.getTProcessor( CompactorService.Processor.class, CompactorService.Iface.class, serviceHandler, context)); + muxProcessor.registerProcessor(METRICS.getServiceName(), METRICS.getTProcessor( + MetricService.Processor.class, MetricService.Iface.class, metricHandler, context)); return muxProcessor; } public static TMultiplexedProcessor getGcTProcessor(GCMonitorService.Iface serviceHandler, - ServerContext context) { + MetricServiceHandler metricHandler, ServerContext context) { TMultiplexedProcessor muxProcessor = new TMultiplexedProcessor(); muxProcessor.registerProcessor(GC.getServiceName(), GC.getTProcessor( GCMonitorService.Processor.class, GCMonitorService.Iface.class, serviceHandler, context)); + muxProcessor.registerProcessor(METRICS.getServiceName(), METRICS.getTProcessor( + MetricService.Processor.class, MetricService.Iface.class, metricHandler, context)); return muxProcessor; } public static TMultiplexedProcessor getManagerTProcessor(FateService.Iface fateServiceHandler, CompactionCoordinatorService.Iface coordinatorServiceHandler, - ManagerClientService.Iface managerServiceHandler, ServerContext context) { + ManagerClientService.Iface managerServiceHandler, MetricServiceHandler metricHandler, + ServerContext context) { TMultiplexedProcessor muxProcessor = new TMultiplexedProcessor(); muxProcessor.registerProcessor(FATE.getServiceName(), FATE.getTProcessor( FateService.Processor.class, FateService.Iface.class, fateServiceHandler, context)); @@ -125,17 +136,22 @@ public static TMultiplexedProcessor getManagerTProcessor(FateService.Iface fateS muxProcessor.registerProcessor(MANAGER.getServiceName(), MANAGER.getTProcessor(ManagerClientService.Processor.class, ManagerClientService.Iface.class, managerServiceHandler, context)); + muxProcessor.registerProcessor(METRICS.getServiceName(), METRICS.getTProcessor( + MetricService.Processor.class, MetricService.Iface.class, metricHandler, context)); return muxProcessor; } public static TMultiplexedProcessor getScanServerTProcessor(ClientServiceHandler clientHandler, - TabletScanClientService.Iface tserverHandler, ServerContext context) { + TabletScanClientService.Iface tserverHandler, MetricServiceHandler metricHandler, + ServerContext context) { TMultiplexedProcessor muxProcessor = new TMultiplexedProcessor(); muxProcessor.registerProcessor(CLIENT.getServiceName(), CLIENT.getTProcessor( ClientService.Processor.class, ClientService.Iface.class, clientHandler, context)); muxProcessor.registerProcessor(TABLET_SCAN.getServiceName(), TABLET_SCAN.getTProcessor(TabletScanClientService.Processor.class, TabletScanClientService.Iface.class, tserverHandler, context)); + muxProcessor.registerProcessor(METRICS.getServiceName(), METRICS.getTProcessor( + MetricService.Processor.class, MetricService.Iface.class, metricHandler, context)); return muxProcessor; } @@ -143,7 +159,8 @@ public static TMultiplexedProcessor getTabletServerTProcessor(ClientServiceHandl TabletServerClientService.Iface tserverHandler, TabletScanClientService.Iface tserverScanHandler, TabletIngestClientService.Iface tserverIngestHandler, - TabletManagementClientService.Iface tserverMgmtHandler, ServerContext context) { + TabletManagementClientService.Iface tserverMgmtHandler, MetricServiceHandler metricHandler, + ServerContext context) { TMultiplexedProcessor muxProcessor = new TMultiplexedProcessor(); muxProcessor.registerProcessor(CLIENT.getServiceName(), CLIENT.getTProcessor( ClientService.Processor.class, ClientService.Iface.class, clientHandler, context)); @@ -159,6 +176,8 @@ public static TMultiplexedProcessor getTabletServerTProcessor(ClientServiceHandl muxProcessor.registerProcessor(TABLET_MGMT.getServiceName(), TABLET_MGMT.getTProcessor(TabletManagementClientService.Processor.class, TabletManagementClientService.Iface.class, tserverMgmtHandler, context)); + muxProcessor.registerProcessor(METRICS.getServiceName(), METRICS.getTProcessor( + MetricService.Processor.class, MetricService.Iface.class, metricHandler, context)); return muxProcessor; } diff --git a/server/compactor/src/main/java/org/apache/accumulo/compactor/Compactor.java b/server/compactor/src/main/java/org/apache/accumulo/compactor/Compactor.java index 77d8651bec5..c9ac5e6a517 100644 --- a/server/compactor/src/main/java/org/apache/accumulo/compactor/Compactor.java +++ b/server/compactor/src/main/java/org/apache/accumulo/compactor/Compactor.java @@ -88,6 +88,7 @@ import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType; import org.apache.accumulo.core.metrics.MetricsInfo; import org.apache.accumulo.core.metrics.MetricsProducer; +import org.apache.accumulo.core.metrics.thrift.MetricSource; import org.apache.accumulo.core.rpc.ThriftUtil; import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; import org.apache.accumulo.core.securityImpl.thrift.TCredentials; @@ -115,6 +116,7 @@ import org.apache.accumulo.server.compaction.RetryableThriftCall.RetriesExceededException; import org.apache.accumulo.server.conf.TableConfiguration; import org.apache.accumulo.server.fs.VolumeManager; +import org.apache.accumulo.server.metrics.MetricServiceHandler; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TServerUtils; import org.apache.accumulo.server.rpc.ThriftProcessorTypes; @@ -336,12 +338,14 @@ protected CompactorService.Iface getCompactorThriftHandlerInterface() { protected ServerAddress startCompactorClientService() throws UnknownHostException { ClientServiceHandler clientHandler = new ClientServiceHandler(getContext()); + MetricServiceHandler metricHandler = createMetricServiceHandler(MetricSource.COMPACTOR); var processor = ThriftProcessorTypes.getCompactorTProcessor(clientHandler, - getCompactorThriftHandlerInterface(), getContext()); + getCompactorThriftHandlerInterface(), metricHandler, getContext()); ServerAddress sp = TServerUtils.startServer(getContext(), getHostname(), Property.COMPACTOR_CLIENTPORT, processor, this.getClass().getSimpleName(), "Thrift Client Server", Property.COMPACTOR_PORTSEARCH, Property.COMPACTOR_MINTHREADS, Property.COMPACTOR_MINTHREADS_TIMEOUT, Property.COMPACTOR_THREADCHECK); + metricHandler.setHost(sp.address); LOG.info("address = {}", sp.address); return sp; } diff --git a/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java b/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java index bab1d89105a..9ec04d36387 100644 --- a/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java +++ b/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java @@ -50,6 +50,7 @@ import org.apache.accumulo.core.metadata.AccumuloTable; import org.apache.accumulo.core.metadata.schema.Ample.DataLevel; import org.apache.accumulo.core.metrics.MetricsInfo; +import org.apache.accumulo.core.metrics.thrift.MetricSource; import org.apache.accumulo.core.securityImpl.thrift.TCredentials; import org.apache.accumulo.core.spi.balancer.TableLoadBalancer; import org.apache.accumulo.core.trace.TraceUtil; @@ -64,6 +65,7 @@ import org.apache.accumulo.server.conf.TableConfiguration; import org.apache.accumulo.server.fs.VolumeManager; import org.apache.accumulo.server.manager.LiveTServerSet; +import org.apache.accumulo.server.metrics.MetricServiceHandler; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TServerUtils; import org.apache.accumulo.server.rpc.ThriftProcessorTypes; @@ -397,7 +399,8 @@ public void unableToMonitorLockNode(final Exception e) { } private HostAndPort startStatsService() { - var processor = ThriftProcessorTypes.getGcTProcessor(this, getContext()); + MetricServiceHandler metricHandler = createMetricServiceHandler(MetricSource.GARBAGE_COLLECTOR); + var processor = ThriftProcessorTypes.getGcTProcessor(this, metricHandler, getContext()); IntStream port = getConfiguration().getPortStream(Property.GC_PORT); HostAndPort[] addresses = TServerUtils.getHostAndPorts(getHostname(), port); long maxMessageSize = getConfiguration().getAsBytes(Property.RPC_MAX_MESSAGE_SIZE); @@ -407,6 +410,7 @@ private HostAndPort startStatsService() { getContext().getServerSslParams(), getContext().getSaslParams(), 0, getConfiguration().getCount(Property.RPC_BACKLOG), getContext().getMetricsInfo(), false, addresses); + metricHandler.setHost(server.address); log.debug("Starting garbage collector listening on " + server.address); return server.address; } diff --git a/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java b/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java index 20e72ca3af0..78b6f6dc466 100644 --- a/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java +++ b/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java @@ -108,6 +108,7 @@ import org.apache.accumulo.core.metadata.schema.TabletMetadata; import org.apache.accumulo.core.metrics.MetricsInfo; import org.apache.accumulo.core.metrics.MetricsProducer; +import org.apache.accumulo.core.metrics.thrift.MetricSource; import org.apache.accumulo.core.spi.balancer.BalancerEnvironment; import org.apache.accumulo.core.spi.balancer.SimpleLoadBalancer; import org.apache.accumulo.core.spi.balancer.TabletBalancer; @@ -143,6 +144,7 @@ import org.apache.accumulo.server.manager.state.TabletServerState; import org.apache.accumulo.server.manager.state.TabletStateStore; import org.apache.accumulo.server.manager.state.UnassignedTablet; +import org.apache.accumulo.server.metrics.MetricServiceHandler; import org.apache.accumulo.server.rpc.HighlyAvailableServiceWrapper; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TServerUtils; @@ -1121,8 +1123,9 @@ public void run() { HighlyAvailableServiceWrapper.service(managerClientHandler, this); ServerAddress sa; + MetricServiceHandler metricHandler = createMetricServiceHandler(MetricSource.MANAGER); var processor = ThriftProcessorTypes.getManagerTProcessor(fateServiceHandler, - compactionCoordinator.getThriftService(), haProxy, getContext()); + compactionCoordinator.getThriftService(), haProxy, metricHandler, getContext()); try { sa = TServerUtils.startServer(context, getHostname(), Property.MANAGER_CLIENTPORT, processor, @@ -1132,6 +1135,7 @@ public void run() { throw new IllegalStateException("Unable to start server on host " + getHostname(), e); } clientService = sa.server; + metricHandler.setHost(sa.address); log.info("Started Manager client service at {}", sa.address); // block until we can obtain the ZK lock for the manager diff --git a/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServer.java b/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServer.java index 4dc21eb4871..68ba512fd5e 100644 --- a/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServer.java +++ b/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServer.java @@ -82,6 +82,7 @@ import org.apache.accumulo.core.metadata.schema.TabletMetadata; import org.apache.accumulo.core.metadata.schema.TabletsMetadata; import org.apache.accumulo.core.metrics.MetricsInfo; +import org.apache.accumulo.core.metrics.thrift.MetricSource; import org.apache.accumulo.core.securityImpl.thrift.TCredentials; import org.apache.accumulo.core.tabletscan.thrift.ActiveScan; import org.apache.accumulo.core.tabletscan.thrift.ScanServerBusyException; @@ -102,6 +103,7 @@ import org.apache.accumulo.server.compaction.PausedCompactionMetrics; import org.apache.accumulo.server.conf.TableConfiguration; import org.apache.accumulo.server.fs.VolumeManager; +import org.apache.accumulo.server.metrics.MetricServiceHandler; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TServerUtils; import org.apache.accumulo.server.rpc.ThriftProcessorTypes; @@ -304,14 +306,16 @@ protected ServerAddress startScanServerClientService() throws UnknownHostExcepti // This class implements TabletClientService.Iface and then delegates calls. Be sure // to set up the ThriftProcessor using this class, not the delegate. ClientServiceHandler clientHandler = new ClientServiceHandler(context); - TProcessor processor = - ThriftProcessorTypes.getScanServerTProcessor(clientHandler, this, getContext()); + MetricServiceHandler metricHandler = createMetricServiceHandler(MetricSource.SCAN_SERVER); + TProcessor processor = ThriftProcessorTypes.getScanServerTProcessor(clientHandler, this, + metricHandler, getContext()); ServerAddress sp = TServerUtils.startServer(getContext(), getHostname(), Property.SSERV_CLIENTPORT, processor, this.getClass().getSimpleName(), "Thrift Client Server", Property.SSERV_PORTSEARCH, Property.SSERV_MINTHREADS, Property.SSERV_MINTHREADS_TIMEOUT, Property.SSERV_THREADCHECK); + metricHandler.setHost(sp.address); LOG.info("address = {}", sp.address); return sp; } diff --git a/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java b/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java index 5cb59315fea..035b828f315 100644 --- a/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java +++ b/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java @@ -95,6 +95,7 @@ import org.apache.accumulo.core.metadata.schema.Ample; import org.apache.accumulo.core.metadata.schema.TabletMetadata; import org.apache.accumulo.core.metrics.MetricsInfo; +import org.apache.accumulo.core.metrics.thrift.MetricSource; import org.apache.accumulo.core.rpc.ThriftUtil; import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; import org.apache.accumulo.core.spi.fs.VolumeChooserEnvironment; @@ -122,6 +123,7 @@ import org.apache.accumulo.server.fs.VolumeManager; import org.apache.accumulo.server.log.WalStateManager; import org.apache.accumulo.server.log.WalStateManager.WalMarkerException; +import org.apache.accumulo.server.metrics.MetricServiceHandler; import org.apache.accumulo.server.rpc.ServerAddress; import org.apache.accumulo.server.rpc.TServerUtils; import org.apache.accumulo.server.rpc.ThriftProcessorTypes; @@ -464,11 +466,13 @@ private HostAndPort startTabletClientService() throws UnknownHostException { clientHandler = newClientHandler(); thriftClientHandler = newTabletClientHandler(writeTracker); scanClientHandler = newThriftScanClientHandler(writeTracker); + MetricServiceHandler metricHandler = createMetricServiceHandler(MetricSource.TABLET_SERVER); - TProcessor processor = - ThriftProcessorTypes.getTabletServerTProcessor(clientHandler, thriftClientHandler, - scanClientHandler, thriftClientHandler, thriftClientHandler, getContext()); + TProcessor processor = ThriftProcessorTypes.getTabletServerTProcessor(clientHandler, + thriftClientHandler, scanClientHandler, thriftClientHandler, thriftClientHandler, + metricHandler, getContext()); HostAndPort address = startServer(clientAddress.getHost(), processor); + metricHandler.setHost(address); log.info("address = {}", address); return address; } diff --git a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java index 7880f4f8b28..5547496296b 100644 --- a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java +++ b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java @@ -222,7 +222,7 @@ public void confirmMetricsPublished() throws Exception { cluster.stop(); } - private void doWorkToGenerateMetrics() throws Exception { + void doWorkToGenerateMetrics(AccumuloClient client) throws Exception { try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { String tableName = this.getClass().getSimpleName(); client.tableOperations().create(tableName); diff --git a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java new file mode 100644 index 00000000000..38140c2e651 --- /dev/null +++ b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java @@ -0,0 +1,78 @@ +package org.apache.accumulo.test.metrics; + +import java.util.Set; + +import org.apache.accumulo.core.client.Accumulo; +import org.apache.accumulo.core.client.AccumuloClient; +import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.clientImpl.ClientContext; +import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.core.metrics.thrift.MetricService.Client; +import org.apache.accumulo.core.rpc.ThriftUtil; +import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; +import org.apache.accumulo.core.trace.TraceUtil; +import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl; +import org.apache.accumulo.test.functional.ConfigurableMacBase; +import org.apache.hadoop.conf.Configuration; +import org.junit.jupiter.api.Test; + +import com.google.common.net.HostAndPort; + +public class MetricsThriftRPCIT extends ConfigurableMacBase { + + @Override + protected void configure(MiniAccumuloConfigImpl cfg, Configuration hadoopCoreSite) { + cfg.setProperty(Property.GC_CYCLE_START, "1s"); + cfg.setProperty(Property.GC_CYCLE_DELAY, "1s"); + cfg.setProperty(Property.MANAGER_FATE_METRICS_MIN_UPDATE_INTERVAL, "1s"); + cfg.setProperty(Property.GENERAL_MICROMETER_ENABLED, "true"); + cfg.setProperty(Property.GENERAL_MICROMETER_USER_TAGS, "tag1=value1,tag2=value2"); + cfg.setProperty(Property.GENERAL_MICROMETER_CACHE_METRICS_ENABLED, "true"); + cfg.setProperty(Property.GENERAL_MICROMETER_JVM_METRICS_ENABLED, "true"); + } + + @Test + public void testRpc() throws Exception { + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + ClientContext cc = (ClientContext) client; + Set managers = client.instanceOperations().getServers(ServerId.Type.MANAGER); + for (ServerId server : managers) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + Set compactors = client.instanceOperations().getServers(ServerId.Type.COMPACTOR); + for (ServerId server : compactors) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + Set sservers = client.instanceOperations().getServers(ServerId.Type.SCAN_SERVER); + for (ServerId server : sservers) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + Set tservers = client.instanceOperations().getServers(ServerId.Type.TABLET_SERVER); + for (ServerId server : tservers) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + } + } + +} From d9ad2a7fbaf9324aa2646ef1864f65698aafcc58 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Tue, 22 Oct 2024 19:53:53 +0000 Subject: [PATCH 02/22] Got MetricsThriftRpcIT working --- .../server/metrics/MetricResponseWrapper.java | 74 +++++--- .../server/metrics/MetricServiceHandler.java | 29 +-- .../accumulo/test/metrics/MetricsIT.java | 90 +++++----- .../test/metrics/MetricsThriftRPCIT.java | 78 -------- .../test/metrics/MetricsThriftRpcIT.java | 169 ++++++++++++++++++ 5 files changed, 283 insertions(+), 157 deletions(-) delete mode 100644 test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java create mode 100644 test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java index 0106058d719..1451aab8e39 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java @@ -18,11 +18,13 @@ */ package org.apache.accumulo.server.metrics; +import static org.apache.accumulo.core.metrics.MetricsInfo.HOST_TAG_KEY; import static org.apache.accumulo.core.metrics.MetricsInfo.INSTANCE_NAME_TAG_KEY; import static org.apache.accumulo.core.metrics.MetricsInfo.PORT_TAG_KEY; import static org.apache.accumulo.core.metrics.MetricsInfo.PROCESS_NAME_TAG_KEY; import static org.apache.accumulo.core.metrics.MetricsInfo.RESOURCE_GROUP_TAG_KEY; +import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -32,6 +34,7 @@ import org.apache.accumulo.core.metrics.flatbuffers.FMetric; import org.apache.accumulo.core.metrics.flatbuffers.FTag; import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.core.util.ByteBufferUtil; import com.google.flatbuffers.FlatBufferBuilder; @@ -50,29 +53,31 @@ public class MetricResponseWrapper extends MetricResponse { private static final long serialVersionUID = 1L; private final TimeUnit UNIT = TimeUnit.SECONDS; - private final FlatBufferBuilder builder = new FlatBufferBuilder(); + private final FlatBufferBuilder builder = new FlatBufferBuilder(1024); + public void cleanup() { + builder.clear(); + } + private List reduceTags(List tags, List extraTags) { return Stream.concat(tags.stream(), extraTags.stream()).filter(t -> { return !t.getKey().equals(INSTANCE_NAME_TAG_KEY) && !t.getKey().equals(PROCESS_NAME_TAG_KEY) && !t.getKey().equals(RESOURCE_GROUP_TAG_KEY) && - !t.getKey().equals(RESOURCE_GROUP_TAG_KEY) && + !t.getKey().equals(HOST_TAG_KEY) && !t.getKey().equals(PORT_TAG_KEY); }).collect(Collectors.toList()); } - private void addMetric(Meter.Id id, List extraTags, int value) { + private synchronized void addMetric(Meter.Id id, List extraTags, int value) { final String type = id.getType().name(); final String name = id.getName(); final List tags = id.getTags(); builder.clear(); - FMetric.startFMetric(builder); - FMetric.addName(builder, builder.createString(name)); - FMetric.addType(builder, builder.createString(type)); - FMetric.addIvalue(builder, value); + int nameRef = builder.createString(name); + int typeRef = builder.createString(type); List tagList = reduceTags(tags, extraTags); int[] tagRefs = new int[tagList.size()]; for (int idx = 0; idx < tagList.size(); idx++) { @@ -81,22 +86,29 @@ private void addMetric(Meter.Id id, List extraTags, int value) { int v = builder.createString(t.getValue()); tagRefs[idx] = FTag.createFTag(builder, k, v); } - FMetric.createTagsVector(builder, tagRefs); + int tagsRef = FMetric.createTagsVector(builder, tagRefs); + + FMetric.startFMetric(builder); + FMetric.addName(builder, nameRef); + FMetric.addType(builder, typeRef); + FMetric.addTags(builder, tagsRef); + FMetric.addIvalue(builder, value); int metricRef = FMetric.endFMetric(builder); builder.finish(metricRef); - this.addToMetrics(builder.dataBuffer()); + ByteBuffer buf = builder.dataBuffer(); + // We want to copy buf as the above returns a reference + // to a ByteBuffer that is reused by the FlatBufferBuilder + this.addToMetrics(ByteBuffer.wrap(ByteBufferUtil.toBytes(buf))); } - private void addMetric(Meter.Id id, List extraTags, long value) { + private synchronized void addMetric(Meter.Id id, List extraTags, long value) { final String type = id.getType().name(); final String name = id.getName(); final List tags = id.getTags(); builder.clear(); - FMetric.startFMetric(builder); - FMetric.addName(builder, builder.createString(name)); - FMetric.addType(builder, builder.createString(type)); - FMetric.addLvalue(builder, value); + int nameRef = builder.createString(name); + int typeRef = builder.createString(type); List tagList = reduceTags(tags, extraTags); int[] tagRefs = new int[tagList.size()]; for (int idx = 0; idx < tagList.size(); idx++) { @@ -105,22 +117,29 @@ private void addMetric(Meter.Id id, List extraTags, long value) { int v = builder.createString(t.getValue()); tagRefs[idx] = FTag.createFTag(builder, k, v); } - FMetric.createTagsVector(builder, tagRefs); + int tagsRef = FMetric.createTagsVector(builder, tagRefs); + + FMetric.startFMetric(builder); + FMetric.addName(builder, nameRef); + FMetric.addType(builder, typeRef); + FMetric.addTags(builder, tagsRef); + FMetric.addLvalue(builder, value); int metricRef = FMetric.endFMetric(builder); builder.finish(metricRef); - this.addToMetrics(builder.dataBuffer()); + ByteBuffer buf = builder.dataBuffer(); + // We want to copy buf as the above returns a reference + // to a ByteBuffer that is reused by the FlatBufferBuilder + this.addToMetrics(ByteBuffer.wrap(ByteBufferUtil.toBytes(buf))); } - private void addMetric(Meter.Id id, List extraTags, double value) { + private synchronized void addMetric(Meter.Id id, List extraTags, double value) { final String type = id.getType().name(); final String name = id.getName(); final List tags = id.getTags(); builder.clear(); - FMetric.startFMetric(builder); - FMetric.addName(builder, builder.createString(name)); - FMetric.addType(builder, builder.createString(type)); - FMetric.addDvalue(builder, value); + int nameRef = builder.createString(name); + int typeRef = builder.createString(type); List tagList = reduceTags(tags, extraTags); int[] tagRefs = new int[tagList.size()]; for (int idx = 0; idx < tagList.size(); idx++) { @@ -129,10 +148,19 @@ private void addMetric(Meter.Id id, List extraTags, double value) { int v = builder.createString(t.getValue()); tagRefs[idx] = FTag.createFTag(builder, k, v); } - FMetric.createTagsVector(builder, tagRefs); + int tagsRef = FMetric.createTagsVector(builder, tagRefs); + + FMetric.startFMetric(builder); + FMetric.addName(builder, nameRef); + FMetric.addType(builder, typeRef); + FMetric.addTags(builder, tagsRef); + FMetric.addDvalue(builder, value); int metricRef = FMetric.endFMetric(builder); builder.finish(metricRef); - this.addToMetrics(builder.dataBuffer()); + ByteBuffer buf = builder.dataBuffer(); + // We want to copy buf as the above returns a reference + // to a ByteBuffer that is reused by the FlatBufferBuilder + this.addToMetrics(ByteBuffer.wrap(ByteBufferUtil.toBytes(buf))); } public Consumer writeMeter(Meter meter) { diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java index 3531fbcf1d3..70901c8be7e 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java @@ -18,6 +18,7 @@ */ package org.apache.accumulo.server.metrics; +import java.util.ArrayList; import java.util.List; import org.apache.accumulo.core.clientImpl.thrift.SecurityErrorCode; @@ -34,7 +35,7 @@ import com.google.common.net.HostAndPort; -import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; public class MetricServiceHandler implements MetricService.Iface { @@ -74,24 +75,28 @@ public MetricResponse getMetrics(TInfo tinfo, TCredentials credentials) return response; } - if (!ctx.getMetricsInfo().isMetricsEnabled()) { - return response; - } - response.setServerType(type); response.setServer(host); response.setResourceGroup(resourceGroup); response.setTimestamp(System.currentTimeMillis()); - final List meters = Metrics.globalRegistry.getMeters(); - for (Meter m : meters) { - if (m.getId().getName().startsWith("accumulo.")) { - m.use(response::writeMeter, response::writeMeter, response::writeTimer, - response::writeDistributionSummary, response::writeLongTaskTimer, response::writeMeter, - response::writeMeter, response::writeFunctionTimer, response::writeMeter); - } + if (!ctx.getMetricsInfo().isMetricsEnabled()) { + return response; } + List registries = new ArrayList<>(Metrics.globalRegistry.getRegistries()); + registries.add(Metrics.globalRegistry); + registries.forEach(r -> { + r.getMeters().forEach(m -> { + if (m.getId().getName().startsWith("accumulo.")) { + m.match(response::writeMeter, response::writeMeter, response::writeTimer, + response::writeDistributionSummary, response::writeLongTaskTimer, response::writeMeter, + response::writeMeter, response::writeFunctionTimer, response::writeMeter); + } + }); + }); + + response.cleanup(); return response; } diff --git a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java index 5547496296b..5fc3c13c2b4 100644 --- a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java +++ b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java @@ -157,8 +157,8 @@ public void confirmMetricsPublished() throws Exception { AtomicReference error = new AtomicReference<>(); Thread workerThread = new Thread(() -> { - try { - doWorkToGenerateMetrics(); + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + doWorkToGenerateMetrics(client, getClass()); } catch (Exception e) { error.set(e); } @@ -222,49 +222,47 @@ public void confirmMetricsPublished() throws Exception { cluster.stop(); } - void doWorkToGenerateMetrics(AccumuloClient client) throws Exception { - try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { - String tableName = this.getClass().getSimpleName(); - client.tableOperations().create(tableName); - SortedSet splits = new TreeSet<>(List.of(new Text("5"))); - client.tableOperations().addSplits(tableName, splits); - Thread.sleep(3_000); - BatchWriterConfig config = new BatchWriterConfig().setMaxMemory(0); - try (BatchWriter writer = client.createBatchWriter(tableName, config)) { - Mutation m = new Mutation("row"); - m.put("cf", "cq", new Value("value")); - writer.addMutation(m); - } - client.tableOperations().flush(tableName); - try (BatchWriter writer = client.createBatchWriter(tableName, config)) { - Mutation m = new Mutation("row"); + static void doWorkToGenerateMetrics(AccumuloClient client, Class testClass) throws Exception { + String tableName = testClass.getSimpleName(); + client.tableOperations().create(tableName); + SortedSet splits = new TreeSet<>(List.of(new Text("5"))); + client.tableOperations().addSplits(tableName, splits); + Thread.sleep(3_000); + BatchWriterConfig config = new BatchWriterConfig().setMaxMemory(0); + try (BatchWriter writer = client.createBatchWriter(tableName, config)) { + Mutation m = new Mutation("row"); + m.put("cf", "cq", new Value("value")); + writer.addMutation(m); + } + client.tableOperations().flush(tableName); + try (BatchWriter writer = client.createBatchWriter(tableName, config)) { + Mutation m = new Mutation("row"); + m.put("cf", "cq", new Value("value")); + writer.addMutation(m); + } + client.tableOperations().flush(tableName); + try (BatchWriter writer = client.createBatchWriter(tableName, config)) { + for (int i = 0; i < 10; i++) { + Mutation m = new Mutation(i + "_row"); m.put("cf", "cq", new Value("value")); writer.addMutation(m); } - client.tableOperations().flush(tableName); - try (BatchWriter writer = client.createBatchWriter(tableName, config)) { - for (int i = 0; i < 10; i++) { - Mutation m = new Mutation(i + "_row"); - m.put("cf", "cq", new Value("value")); - writer.addMutation(m); - } - } - client.tableOperations().compact(tableName, new CompactionConfig().setWait(true)); - try (Scanner scanner = client.createScanner(tableName)) { - scanner.forEach((k, v) -> {}); - } - // Start a compaction with the slow iterator to ensure that the compaction queues - // are not removed quickly - CompactionConfig cc = new CompactionConfig(); - IteratorSetting is = new IteratorSetting(100, "slow", SlowIterator.class); - SlowIterator.setSleepTime(is, 3000); - cc.setIterators(List.of(is)); - cc.setWait(false); - client.tableOperations().compact(tableName, new CompactionConfig().setWait(true)); - client.tableOperations().delete(tableName); - while (client.tableOperations().exists(tableName)) { - Thread.sleep(1000); - } + } + client.tableOperations().compact(tableName, new CompactionConfig().setWait(true)); + try (Scanner scanner = client.createScanner(tableName)) { + scanner.forEach((k, v) -> {}); + } + // Start a compaction with the slow iterator to ensure that the compaction queues + // are not removed quickly + CompactionConfig cc = new CompactionConfig(); + IteratorSetting is = new IteratorSetting(100, "slow", SlowIterator.class); + SlowIterator.setSleepTime(is, 3000); + cc.setIterators(List.of(is)); + cc.setWait(false); + client.tableOperations().compact(tableName, new CompactionConfig().setWait(true)); + client.tableOperations().delete(tableName); + while (client.tableOperations().exists(tableName)) { + Thread.sleep(1000); } } @@ -276,7 +274,9 @@ public void registerMetrics(MeterRegistry registry) { @Test public void metricTags() throws Exception { - doWorkToGenerateMetrics(); + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + doWorkToGenerateMetrics(client, getClass()); + } cluster.stop(); List statsDMetrics; @@ -310,7 +310,9 @@ public void metricTags() throws Exception { @Test public void fateMetrics() throws Exception { - doWorkToGenerateMetrics(); + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + doWorkToGenerateMetrics(client, getClass()); + } cluster.stop(); List statsDMetrics; diff --git a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java deleted file mode 100644 index 38140c2e651..00000000000 --- a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRPCIT.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.apache.accumulo.test.metrics; - -import java.util.Set; - -import org.apache.accumulo.core.client.Accumulo; -import org.apache.accumulo.core.client.AccumuloClient; -import org.apache.accumulo.core.client.admin.servers.ServerId; -import org.apache.accumulo.core.clientImpl.ClientContext; -import org.apache.accumulo.core.conf.Property; -import org.apache.accumulo.core.metrics.thrift.MetricResponse; -import org.apache.accumulo.core.metrics.thrift.MetricService.Client; -import org.apache.accumulo.core.rpc.ThriftUtil; -import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; -import org.apache.accumulo.core.trace.TraceUtil; -import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl; -import org.apache.accumulo.test.functional.ConfigurableMacBase; -import org.apache.hadoop.conf.Configuration; -import org.junit.jupiter.api.Test; - -import com.google.common.net.HostAndPort; - -public class MetricsThriftRPCIT extends ConfigurableMacBase { - - @Override - protected void configure(MiniAccumuloConfigImpl cfg, Configuration hadoopCoreSite) { - cfg.setProperty(Property.GC_CYCLE_START, "1s"); - cfg.setProperty(Property.GC_CYCLE_DELAY, "1s"); - cfg.setProperty(Property.MANAGER_FATE_METRICS_MIN_UPDATE_INTERVAL, "1s"); - cfg.setProperty(Property.GENERAL_MICROMETER_ENABLED, "true"); - cfg.setProperty(Property.GENERAL_MICROMETER_USER_TAGS, "tag1=value1,tag2=value2"); - cfg.setProperty(Property.GENERAL_MICROMETER_CACHE_METRICS_ENABLED, "true"); - cfg.setProperty(Property.GENERAL_MICROMETER_JVM_METRICS_ENABLED, "true"); - } - - @Test - public void testRpc() throws Exception { - try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { - ClientContext cc = (ClientContext) client; - Set managers = client.instanceOperations().getServers(ServerId.Type.MANAGER); - for (ServerId server : managers) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); - try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - } finally { - ThriftUtil.returnClient(metricsClient, cc); - } - } - Set compactors = client.instanceOperations().getServers(ServerId.Type.COMPACTOR); - for (ServerId server : compactors) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); - try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - } finally { - ThriftUtil.returnClient(metricsClient, cc); - } - } - Set sservers = client.instanceOperations().getServers(ServerId.Type.SCAN_SERVER); - for (ServerId server : sservers) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); - try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - } finally { - ThriftUtil.returnClient(metricsClient, cc); - } - } - Set tservers = client.instanceOperations().getServers(ServerId.Type.TABLET_SERVER); - for (ServerId server : tservers) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); - try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - } finally { - ThriftUtil.returnClient(metricsClient, cc); - } - } - } - } - -} diff --git a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java new file mode 100644 index 00000000000..6319762f10e --- /dev/null +++ b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java @@ -0,0 +1,169 @@ +package org.apache.accumulo.test.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.Accumulo; +import org.apache.accumulo.core.client.AccumuloClient; +import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.clientImpl.ClientContext; +import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.lock.ServiceLockData; +import org.apache.accumulo.core.lock.ServiceLockData.ThriftService; +import org.apache.accumulo.core.lock.ServiceLockPaths.ServiceLockPath; +import org.apache.accumulo.core.metrics.flatbuffers.FMetric; +import org.apache.accumulo.core.metrics.flatbuffers.FTag; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.core.metrics.thrift.MetricService.Client; +import org.apache.accumulo.core.metrics.thrift.MetricSource; +import org.apache.accumulo.core.rpc.ThriftUtil; +import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; +import org.apache.accumulo.core.trace.TraceUtil; +import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl; +import org.apache.accumulo.test.functional.ConfigurableMacBase; +import org.apache.hadoop.conf.Configuration; +import org.junit.jupiter.api.Test; + +import com.google.common.net.HostAndPort; + +public class MetricsThriftRpcIT extends ConfigurableMacBase { + + @Override + protected Duration defaultTimeout() { + return Duration.ofMinutes(3); + } + + @Override + protected void configure(MiniAccumuloConfigImpl cfg, Configuration hadoopCoreSite) { + cfg.setProperty(Property.GC_CYCLE_START, "1s"); + cfg.setProperty(Property.GC_CYCLE_DELAY, "1s"); + cfg.setProperty(Property.MANAGER_FATE_METRICS_MIN_UPDATE_INTERVAL, "1s"); + cfg.setProperty(Property.GENERAL_MICROMETER_ENABLED, "true"); + cfg.setProperty(Property.GENERAL_MICROMETER_USER_TAGS, "tag1=value1,tag2=value2"); + cfg.setProperty(Property.GENERAL_MICROMETER_CACHE_METRICS_ENABLED, "true"); + cfg.setProperty(Property.GENERAL_MICROMETER_JVM_METRICS_ENABLED, "true"); + cfg.getClusterServerConfiguration().setNumDefaultCompactors(1); + cfg.getClusterServerConfiguration().setNumDefaultScanServers(1); + cfg.getClusterServerConfiguration().setNumDefaultTabletServers(1); + cfg.getClusterServerConfiguration().addCompactorResourceGroup("CTEST", 3); + cfg.getClusterServerConfiguration().addScanServerResourceGroup("STEST", 2); + cfg.getClusterServerConfiguration().addTabletServerResourceGroup("TTEST", 1); + } + + private int handleMetrics(final MetricResponse response) { + if (response.getMetricsSize() == 0) { + System.out.println("type: " + response.getServerType() + ", host: " + response.getServer() + + ", group: " + response.getResourceGroup() + "has no metrics"); + return response.getMetricsSize(); + } + for (final ByteBuffer binary : response.getMetrics()) { + FMetric fm = FMetric.getRootAsFMetric(binary); + final List tags = new ArrayList<>(fm.tagsLength()); + for (int i = 0; i < fm.tagsLength(); i++) { + FTag t = fm.tags(i); + tags.add(t.key() + " = " + t.value()); + } + System.out.println("type: " + response.getServerType() + ", host: " + response.getServer() + + ", group: " + response.getResourceGroup() + ", time: " + response.getTimestamp() + + ", name: " + fm.name() + ", type: " + fm.type() + ", tags: " + tags + + ", dval: " + fm.dvalue() + ", ival: " + fm.ivalue() + ", lval: " + fm.lvalue()); + } + return response.getMetricsSize(); + } + + @Test + public void testRpc() throws Exception { + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + MetricsIT.doWorkToGenerateMetrics(client, getClass()); + // Wait one minute for server to collect some metrics + Thread.sleep(60_000); + ClientContext cc = (ClientContext) client; + Set managers = client.instanceOperations().getServers(ServerId.Type.MANAGER); + assertEquals(1, managers.size()); + for (ServerId server : managers) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.MANAGER, response.getServerType()); + assertTrue(handleMetrics(response) > 0); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + ServiceLockPath zgcPath = cc.getServerPaths().getGarbageCollector(true); + if (zgcPath != null) { + Optional sld = cc.getZooCache().getLockData(zgcPath); + String location = null; + if (sld.isPresent()) { + location = sld.orElseThrow().getAddressString(ThriftService.GC); + HostAndPort hp = HostAndPort.fromString(location); + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, hp, cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), + getCluster().getServerContext().rpcCreds()); + assertEquals(Constants.DEFAULT_RESOURCE_GROUP_NAME, response.getResourceGroup()); + assertEquals(MetricSource.GARBAGE_COLLECTOR, response.getServerType()); + assertTrue(handleMetrics(response) > 0); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } else { + fail("Garbage Collector ZooKeeper lock data not found"); + } + } else { + fail("Garbage Collector not found in ZooKeeper"); + } + + Set compactors = client.instanceOperations().getServers(ServerId.Type.COMPACTOR); + assertEquals(4, compactors.size()); + for (ServerId server : compactors) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.COMPACTOR, response.getServerType()); + assertTrue(handleMetrics(response) > 0); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + Set sservers = client.instanceOperations().getServers(ServerId.Type.SCAN_SERVER); + assertEquals(3, sservers.size()); + for (ServerId server : sservers) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.SCAN_SERVER, response.getServerType()); + assertTrue(handleMetrics(response) > 0); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + Set tservers = client.instanceOperations().getServers(ServerId.Type.TABLET_SERVER); + assertEquals(2, tservers.size()); + for (ServerId server : tservers) { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.TABLET_SERVER, response.getServerType()); + assertTrue(handleMetrics(response) > 0); + } finally { + ThriftUtil.returnClient(metricsClient, cc); + } + } + } + } + +} From 72df86f552421c0dabe615e396c188b6e0840300 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Wed, 23 Oct 2024 21:20:40 +0000 Subject: [PATCH 03/22] Wired up new monitor endpoint --- assemble/pom.xml | 55 ++++ .../core/client/admin/servers/ServerId.java | 2 +- .../accumulo/core/metrics/MetricsInfo.java | 2 +- core/src/main/spotbugs/exclude-filter.xml | 4 +- pom.xml | 28 +- server/base/pom.xml | 4 + .../server/metrics/MetricResponseWrapper.java | 132 ++++---- .../server/metrics/MetricServiceHandler.java | 11 +- server/monitor/pom.xml | 30 ++ .../accumulo/monitor/MetricsFetcher.java | 288 ++++++++++++++++++ .../org/apache/accumulo/monitor/Monitor.java | 9 + .../apache/accumulo/monitor/NewMonitor.java | 135 ++++++++ .../test/metrics/MetricsThriftRpcIT.java | 86 ++++-- 13 files changed, 679 insertions(+), 107 deletions(-) create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java diff --git a/assemble/pom.xml b/assemble/pom.xml index a1605561ec0..8b1773d32f8 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -81,6 +81,11 @@ gson true + + com.google.flatbuffers + flatbuffers-java + true + com.google.guava failureaccess @@ -121,6 +126,11 @@ commons-logging true + + io.javalin + javalin + true + io.micrometer micrometer-commons @@ -343,6 +353,36 @@ jetty-util true + + org.eclipse.jetty.websocket + websocket-core-common + true + + + org.eclipse.jetty.websocket + websocket-core-server + true + + + org.eclipse.jetty.websocket + websocket-jetty-api + true + + + org.eclipse.jetty.websocket + websocket-jetty-common + true + + + org.eclipse.jetty.websocket + websocket-jetty-server + true + + + org.eclipse.jetty.websocket + websocket-servlet + true + org.freemarker freemarker @@ -468,6 +508,21 @@ jboss-logging true + + org.jetbrains.kotlin + kotlin-stdlib + true + + + org.jetbrains.kotlin + kotlin-stdlib-jdk7 + true + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + true + org.jline jline diff --git a/core/src/main/java/org/apache/accumulo/core/client/admin/servers/ServerId.java b/core/src/main/java/org/apache/accumulo/core/client/admin/servers/ServerId.java index b1c7a44d2b0..7beb15b0530 100644 --- a/core/src/main/java/org/apache/accumulo/core/client/admin/servers/ServerId.java +++ b/core/src/main/java/org/apache/accumulo/core/client/admin/servers/ServerId.java @@ -29,7 +29,7 @@ * * @since 4.0.0 */ -public final class ServerId implements Comparable { +public class ServerId implements Comparable { /** * Server process type names that a client can be expected to interact with. Clients are not diff --git a/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java b/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java index 92173adecf3..3a8247a947c 100644 --- a/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java +++ b/core/src/main/java/org/apache/accumulo/core/metrics/MetricsInfo.java @@ -57,7 +57,7 @@ static Tag processTag(final String processName) { Objects.requireNonNull(processName, "cannot create the tag without providing the process name"); return Tag.of(PROCESS_NAME_TAG_KEY, processName); } - + /** * Convenience method to create tag name / value pair for the resource group name * diff --git a/core/src/main/spotbugs/exclude-filter.xml b/core/src/main/spotbugs/exclude-filter.xml index ded70a98dc4..9546d6bf7e8 100644 --- a/core/src/main/spotbugs/exclude-filter.xml +++ b/core/src/main/spotbugs/exclude-filter.xml @@ -25,7 +25,7 @@ Exceptions can be made if the bug is particularly spammy or trivial. --> - + @@ -38,6 +38,8 @@ + + diff --git a/pom.xml b/pom.xml index 72cecc21c56..fbd99186c1e 100644 --- a/pom.xml +++ b/pom.xml @@ -206,7 +206,7 @@ org.eclipse.jetty jetty-bom - 11.0.19 + 11.0.23 pom import @@ -322,6 +322,11 @@ commons-logging 1.3.3 + + io.javalin + javalin + 6.3.0 + org.apache.accumulo accumulo-access @@ -598,6 +603,22 @@ jboss-logging-processor 2.2.1.Final + + + org.jetbrains.kotlin + kotlin-stdlib + 1.9.25 + + + org.jetbrains.kotlin + kotlin-stdlib-jdk7 + 1.9.25 + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + 1.9.25 + org.jline jline @@ -674,6 +695,7 @@ SLASHSTAR_STYLE SLASHSTAR_STYLE SLASHSTAR_STYLE + SLASHSTAR_STYLE @@ -1115,7 +1137,7 @@ warning true - **/thrift/*.java + **/thrift/*.java,**/flatbuffers/*.java @@ -1647,7 +1669,7 @@ -XDcompilePolicy=simple -Xplugin:ErrorProne \ - -XepExcludedPaths:.*/(thrift|generated-sources|src/test)/.* \ + -XepExcludedPaths:.*/(flatbuffers|thrift|generated-sources|src/test)/.* \ -XepDisableWarningsInGeneratedCode \ -XepDisableAllWarnings \ diff --git a/server/base/pom.xml b/server/base/pom.xml index c20b3e6edce..e308e44a3b8 100644 --- a/server/base/pom.xml +++ b/server/base/pom.xml @@ -48,6 +48,10 @@ com.google.code.gson gson + + com.google.flatbuffers + flatbuffers-java + com.google.guava guava diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java index 1451aab8e39..ff60e1a4fce 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java @@ -48,52 +48,76 @@ import io.micrometer.core.instrument.distribution.HistogramSnapshot; import io.micrometer.core.instrument.distribution.ValueAtPercentile; +/** + * This class is not thread-safe + */ public class MetricResponseWrapper extends MetricResponse { + private class CommonRefs { + int nameRef; + int typeRef; + int tagsRef; + + private void reset(int nameRef, int typeRef, int tagsRef) { + this.nameRef = nameRef; + this.typeRef = typeRef; + this.tagsRef = tagsRef; + } + + } + private static final long serialVersionUID = 1L; private final TimeUnit UNIT = TimeUnit.SECONDS; - private final FlatBufferBuilder builder = new FlatBufferBuilder(1024); + private transient final FlatBufferBuilder builder; + private transient final CommonRefs common = new CommonRefs(); - public void cleanup() { - builder.clear(); + public MetricResponseWrapper(FlatBufferBuilder builder) { + this.builder = builder; } - + + /** + * Remove tags from the Metric that duplicate other information found in the MetricResponse + */ private List reduceTags(List tags, List extraTags) { return Stream.concat(tags.stream(), extraTags.stream()).filter(t -> { - return !t.getKey().equals(INSTANCE_NAME_TAG_KEY) && - !t.getKey().equals(PROCESS_NAME_TAG_KEY) && - !t.getKey().equals(RESOURCE_GROUP_TAG_KEY) && - !t.getKey().equals(HOST_TAG_KEY) && - !t.getKey().equals(PORT_TAG_KEY); - }).collect(Collectors.toList()); + return !t.getKey().equals(INSTANCE_NAME_TAG_KEY) && !t.getKey().equals(PROCESS_NAME_TAG_KEY) + && !t.getKey().equals(RESOURCE_GROUP_TAG_KEY) && !t.getKey().equals(HOST_TAG_KEY) + && !t.getKey().equals(PORT_TAG_KEY); + }).collect(Collectors.toList()); } - - private synchronized void addMetric(Meter.Id id, List extraTags, int value) { + private void parseAndCreateCommonInfo(Meter.Id id, List extraTags) { + builder.clear(); final String type = id.getType().name(); final String name = id.getName(); final List tags = id.getTags(); - builder.clear(); - int nameRef = builder.createString(name); - int typeRef = builder.createString(type); - List tagList = reduceTags(tags, extraTags); - int[] tagRefs = new int[tagList.size()]; + final int nameRef = builder.createString(name); + final int typeRef = builder.createString(type); + final List tagList = reduceTags(tags, extraTags); + final int[] tagRefs = new int[tagList.size()]; for (int idx = 0; idx < tagList.size(); idx++) { Tag t = tagList.get(idx); - int k = builder.createString(t.getKey()); - int v = builder.createString(t.getValue()); + final int k = builder.createString(t.getKey()); + final int v = builder.createString(t.getValue()); tagRefs[idx] = FTag.createFTag(builder, k, v); } - int tagsRef = FMetric.createTagsVector(builder, tagRefs); - + final int tagsRef = FMetric.createTagsVector(builder, tagRefs); + common.reset(nameRef, typeRef, tagsRef); + } + + private void addMetric(Meter.Id id, List extraTags, int value) { + builder.clear(); + + parseAndCreateCommonInfo(id, extraTags); + FMetric.startFMetric(builder); - FMetric.addName(builder, nameRef); - FMetric.addType(builder, typeRef); - FMetric.addTags(builder, tagsRef); + FMetric.addName(builder, common.nameRef); + FMetric.addType(builder, common.typeRef); + FMetric.addTags(builder, common.tagsRef); FMetric.addIvalue(builder, value); - int metricRef = FMetric.endFMetric(builder); + final int metricRef = FMetric.endFMetric(builder); builder.finish(metricRef); ByteBuffer buf = builder.dataBuffer(); // We want to copy buf as the above returns a reference @@ -101,30 +125,17 @@ private synchronized void addMetric(Meter.Id id, List extraTags, int value) this.addToMetrics(ByteBuffer.wrap(ByteBufferUtil.toBytes(buf))); } - private synchronized void addMetric(Meter.Id id, List extraTags, long value) { - final String type = id.getType().name(); - final String name = id.getName(); - final List tags = id.getTags(); - + private void addMetric(Meter.Id id, List extraTags, long value) { builder.clear(); - int nameRef = builder.createString(name); - int typeRef = builder.createString(type); - List tagList = reduceTags(tags, extraTags); - int[] tagRefs = new int[tagList.size()]; - for (int idx = 0; idx < tagList.size(); idx++) { - Tag t = tagList.get(idx); - int k = builder.createString(t.getKey()); - int v = builder.createString(t.getValue()); - tagRefs[idx] = FTag.createFTag(builder, k, v); - } - int tagsRef = FMetric.createTagsVector(builder, tagRefs); - + + parseAndCreateCommonInfo(id, extraTags); + FMetric.startFMetric(builder); - FMetric.addName(builder, nameRef); - FMetric.addType(builder, typeRef); - FMetric.addTags(builder, tagsRef); + FMetric.addName(builder, common.nameRef); + FMetric.addType(builder, common.typeRef); + FMetric.addTags(builder, common.tagsRef); FMetric.addLvalue(builder, value); - int metricRef = FMetric.endFMetric(builder); + final int metricRef = FMetric.endFMetric(builder); builder.finish(metricRef); ByteBuffer buf = builder.dataBuffer(); // We want to copy buf as the above returns a reference @@ -132,30 +143,17 @@ private synchronized void addMetric(Meter.Id id, List extraTags, long value this.addToMetrics(ByteBuffer.wrap(ByteBufferUtil.toBytes(buf))); } - private synchronized void addMetric(Meter.Id id, List extraTags, double value) { - final String type = id.getType().name(); - final String name = id.getName(); - final List tags = id.getTags(); - + private void addMetric(Meter.Id id, List extraTags, double value) { builder.clear(); - int nameRef = builder.createString(name); - int typeRef = builder.createString(type); - List tagList = reduceTags(tags, extraTags); - int[] tagRefs = new int[tagList.size()]; - for (int idx = 0; idx < tagList.size(); idx++) { - Tag t = tagList.get(idx); - int k = builder.createString(t.getKey()); - int v = builder.createString(t.getValue()); - tagRefs[idx] = FTag.createFTag(builder, k, v); - } - int tagsRef = FMetric.createTagsVector(builder, tagRefs); - + + parseAndCreateCommonInfo(id, extraTags); + FMetric.startFMetric(builder); - FMetric.addName(builder, nameRef); - FMetric.addType(builder, typeRef); - FMetric.addTags(builder, tagsRef); + FMetric.addName(builder, common.nameRef); + FMetric.addType(builder, common.typeRef); + FMetric.addTags(builder, common.tagsRef); FMetric.addDvalue(builder, value); - int metricRef = FMetric.endFMetric(builder); + final int metricRef = FMetric.endFMetric(builder); builder.finish(metricRef); ByteBuffer buf = builder.dataBuffer(); // We want to copy buf as the above returns a reference diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java index 70901c8be7e..1c28d1d96ef 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java @@ -34,6 +34,7 @@ import org.slf4j.LoggerFactory; import com.google.common.net.HostAndPort; +import com.google.flatbuffers.FlatBufferBuilder; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; @@ -68,7 +69,8 @@ public MetricResponse getMetrics(TInfo tinfo, TCredentials credentials) SecurityErrorCode.PERMISSION_DENIED); } - final MetricResponseWrapper response = new MetricResponseWrapper(); + final FlatBufferBuilder builder = new FlatBufferBuilder(1024); + final MetricResponseWrapper response = new MetricResponseWrapper(builder); if (host == null) { LOG.error("Host is not set, this should have been done after starting the Thrift service."); @@ -90,13 +92,14 @@ public MetricResponse getMetrics(TInfo tinfo, TCredentials credentials) r.getMeters().forEach(m -> { if (m.getId().getName().startsWith("accumulo.")) { m.match(response::writeMeter, response::writeMeter, response::writeTimer, - response::writeDistributionSummary, response::writeLongTaskTimer, response::writeMeter, - response::writeMeter, response::writeFunctionTimer, response::writeMeter); + response::writeDistributionSummary, response::writeLongTaskTimer, + response::writeMeter, response::writeMeter, response::writeFunctionTimer, + response::writeMeter); } }); }); - response.cleanup(); + builder.clear(); return response; } diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index 789607b163a..28be36533dd 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -31,6 +31,10 @@ Apache Accumulo Monitor Server A web server for monitoring Apache Accumulo. + + com.fasterxml.jackson.core + jackson-core + com.fasterxml.jackson.core jackson-databind @@ -52,6 +56,24 @@ com.google.guava guava + + io.javalin + javalin + + + org.slf4j + slf4j-api + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty.websocket + websocket-jetty-server + + + jakarta.inject jakarta.inject-api @@ -104,6 +126,10 @@ org.apache.zookeeper zookeeper + + org.checkerframework + checker-qual + org.eclipse.jetty jetty-server @@ -116,6 +142,10 @@ org.eclipse.jetty jetty-util + + org.eclipse.jetty.websocket + websocket-jetty-server + org.glassfish.hk2 hk2-api diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java new file mode 100644 index 00000000000..d86c4cad327 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.lock.ServiceLockData; +import org.apache.accumulo.core.lock.ServiceLockData.ThriftService; +import org.apache.accumulo.core.lock.ServiceLockPaths.ServiceLockPath; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.core.metrics.thrift.MetricService.Client; +import org.apache.accumulo.core.rpc.ThriftUtil; +import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; +import org.apache.accumulo.core.trace.TraceUtil; +import org.apache.accumulo.core.util.UtilWaitThread; +import org.apache.accumulo.core.util.threads.ThreadPools; +import org.apache.accumulo.core.util.threads.ThreadPools.ExecutionError; +import org.apache.accumulo.server.ServerContext; +import org.apache.thrift.TException; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalCause; +import com.github.benmanes.caffeine.cache.RemovalListener; +import com.github.benmanes.caffeine.cache.Scheduler; +import com.google.common.net.HostAndPort; + +public class MetricsFetcher implements RemovalListener, Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(MetricsFetcher.class); + + private static class GcServerId extends ServerId { + private GcServerId(String resourceGroup, String host, int port) { + // TODO: This is a little wonky, Type.GC does not exist in the public API, + super(ServerId.Type.MANAGER, resourceGroup, host, port); + } + } + + private class MetricFetcher implements Runnable { + + private final ServerContext ctx; + private final ServerId server; + + private MetricFetcher(ServerContext ctx, ServerId server) { + this.ctx = ctx; + this.server = server; + } + + @Override + public void run() { + try { + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, + HostAndPort.fromParts(server.getHost(), server.getPort()), ctx); + try { + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), ctx.rpcCreds()); + problemHosts.remove(server); + allMetrics.put(server, response); + switch (response.serverType) { + case COMPACTOR: + compactors.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()) + .add(server); + break; + case GARBAGE_COLLECTOR: + if (gc.get() == null || !gc.get().equals(server)) { + gc.set(server); + } + break; + case MANAGER: + if (manager.get() == null || !manager.get().equals(server)) { + manager.set(server); + } + break; + case SCAN_SERVER: + sservers.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()) + .add(server); + break; + case TABLET_SERVER: + tservers.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()) + .add(server); + break; + default: + break; + + } + } finally { + ThriftUtil.returnClient(metricsClient, ctx); + } + } catch (TException e) { + LOG.warn("Error trying to get metrics from server: {}", server); + // Error talking to server, add to problem hosts and remove from everything else + problemHosts.add(server); + gc.compareAndSet(server, null); + manager.compareAndSet(server, null); + compactors.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); + sservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); + tservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); + } + } + + } + + private static final Set EMPTY_MUTABLE_SET = new HashSet<>(); + + private final String poolName = "MonitorMetricsThreadPool"; + private final ThreadPoolExecutor pool = ThreadPools.getServerThreadPools() + .getPoolBuilder(poolName).numCoreThreads(10).withTimeOut(30, SECONDS).build(); + + private final ServerContext ctx; + private final Cache allMetrics; + private final Set problemHosts = new HashSet<>(); + private final AtomicReference manager = new AtomicReference<>(); + private final AtomicReference gc = new AtomicReference<>(); + private final ConcurrentHashMap> compactors = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> sservers = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> tservers = new ConcurrentHashMap<>(); + + public MetricsFetcher(ServerContext ctx) { + this.ctx = ctx; + this.allMetrics = Caffeine.newBuilder().executor(pool).scheduler(Scheduler.systemScheduler()) + .expireAfterWrite(Duration.ofMinutes(10)).removalListener(this::onRemoval).build(); + } + + @Override + public void onRemoval(@Nullable ServerId server, @Nullable MetricResponse response, + RemovalCause cause) { + if (server == null) { + return; + } + if (server instanceof GcServerId) { + gc.compareAndSet(server, null); + } else { + switch (server.getType()) { + case COMPACTOR: + compactors.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); + break; + case MANAGER: + manager.compareAndSet(server, null); + break; + case SCAN_SERVER: + sservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); + break; + case TABLET_SERVER: + tservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); + break; + default: + LOG.error("Unhandled server type sent to onRemoval: {}", server); + break; + } + } + } + + @Override + public void run() { + + while (true) { + + final List> futures = new ArrayList<>(); + + for (ServerId.Type type : ServerId.Type.values()) { + for (ServerId server : this.ctx.instanceOperations().getServers(type)) { + futures.add(this.pool.submit(new MetricFetcher(this.ctx, server))); + } + } + ThreadPools.resizePool(pool, () -> Math.max(20, (futures.size() / 20)), poolName); + + // GC is not a public type, add it + ServiceLockPath zgcPath = this.ctx.getServerPaths().getGarbageCollector(true); + if (zgcPath != null) { + Optional sld = this.ctx.getZooCache().getLockData(zgcPath); + if (sld.isPresent()) { + String location = sld.orElseThrow().getAddressString(ThriftService.GC); + String resourceGroup = sld.orElseThrow().getGroup(ThriftService.GC); + HostAndPort hp = HostAndPort.fromString(location); + futures.add(this.pool.submit(new MetricFetcher(this.ctx, + new GcServerId(resourceGroup, hp.getHost(), hp.getPort())))); + } + } + + while (futures.size() > 0) { + Iterator> iter = futures.iterator(); + while (iter.hasNext()) { + Future future = iter.next(); + if (future.isDone()) { + iter.remove(); + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + // throwing an error so the Monitor will die + throw new ExecutionError("Error getting metrics from server", e); + } + } + } + if (futures.size() > 0) { + UtilWaitThread.sleep(3_000); + } + } + + UtilWaitThread.sleep(30_000); + } + + } + + public Set getResourceGroups() { + Set groups = new HashSet<>(); + groups.add(Constants.DEFAULT_RESOURCE_GROUP_NAME); + groups.addAll(compactors.keySet()); + groups.addAll(sservers.keySet()); + groups.addAll(tservers.keySet()); + return groups; + } + + public Collection getAll() { + return allMetrics.asMap().values(); + } + + public MetricResponse getManager() { + ServerId s = manager.get(); + if (s == null) { + return null; + } + return allMetrics.asMap().get(s); + } + + public MetricResponse getGarbageCollector() { + ServerId s = gc.get(); + if (s == null) { + return null; + } + return allMetrics.asMap().get(s); + } + + public Collection getCompactors(String resourceGroup) { + if (!compactors.containsKey(resourceGroup)) { + throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + } + return allMetrics.getAllPresent(compactors.get(resourceGroup)).values(); + } + + public Collection getSServers(String resourceGroup) { + if (!sservers.containsKey(resourceGroup)) { + throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + } + return allMetrics.getAllPresent(sservers.get(resourceGroup)).values(); + } + + public Collection getTServers(String resourceGroup) { + if (!tservers.containsKey(resourceGroup)) { + throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + } + return allMetrics.getAllPresent(tservers.get(resourceGroup)).values(); + } + +} diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java index 99855f806e4..9798f7dceaa 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java @@ -22,6 +22,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.HOURS; +import java.io.IOException; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; @@ -524,6 +525,14 @@ public void run() { }).start(); monitorInitialized.set(true); + + // Start up new Monitor + NewMonitor newMon = new NewMonitor(getContext(), getHostname()); + try { + newMon.start(); + } catch (IOException e) { + log.error("Unable to start new web server", e); + } } private ServletHolder getDefaultServlet() { diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java new file mode 100644 index 00000000000..e677049c22e --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.nio.ByteBuffer; + +import org.apache.accumulo.core.metrics.flatbuffers.FMetric; +import org.apache.accumulo.core.metrics.flatbuffers.FTag; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.core.util.threads.Threads; +import org.apache.accumulo.server.ServerContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.javalin.Javalin; +import io.javalin.json.JavalinJackson; +import io.javalin.security.RouteRole; + +public class NewMonitor { + + public static class MetricResponseSerializer extends JsonSerializer { + + @Override + public void serialize(MetricResponse value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeStartObject(); + gen.writeNumberField("timestamp", value.getTimestamp()); + gen.writeStringField("serverType", value.getServerType().toString()); + gen.writeStringField("resourceGroup", value.getResourceGroup()); + gen.writeStringField("host", value.getServer()); + gen.writeArrayFieldStart("metrics"); + for (final ByteBuffer binary : value.getMetrics()) { + FMetric fm = FMetric.getRootAsFMetric(binary); + gen.writeStartObject(); + gen.writeStringField("name", fm.name()); + gen.writeStringField("type", fm.type()); + gen.writeArrayFieldStart("tags"); + for (int i = 0; i < fm.tagsLength(); i++) { + FTag t = fm.tags(i); + gen.writeStartObject(); + gen.writeStringField(t.key(), t.value()); + gen.writeEndObject(); + } + gen.writeEndArray(); + // Write the non-zero number as the value + if (fm.lvalue() > 0) { + gen.writeNumberField("value", fm.lvalue()); + } else if (fm.ivalue() > 0) { + gen.writeNumberField("value", fm.ivalue()); + } else if (fm.dvalue() > 0.0d) { + gen.writeNumberField("value", fm.dvalue()); + } else { + gen.writeNumberField("value", 0); + } + gen.writeEndObject(); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + + } + + private static final Logger LOG = LoggerFactory.getLogger(NewMonitor.class); + + private final ServerContext ctx; + private final String hostname; + + public NewMonitor(ServerContext ctx, String hostname) { + this.ctx = ctx; + this.hostname = hostname; + } + + @SuppressFBWarnings(value = "UNENCRYPTED_SERVER_SOCKET", + justification = "TODO Replace before merging") + public void start() throws IOException { + + MetricsFetcher metrics = new MetricsFetcher(ctx); + + Threads.createThread("Metric Fetcher Thread", metrics).start(); + + // Find a free socket + ServerSocket ss = new ServerSocket(); + ss.setReuseAddress(true); + ss.bind(new InetSocketAddress(hostname, 0)); + ss.close(); + final int port = ss.getLocalPort(); + + Javalin.create(config -> { + config.bundledPlugins.enableDevLogging(); + config.bundledPlugins.enableRouteOverview("/routes", new RouteRole[] {}); + config.jsonMapper(new JavalinJackson().updateMapper(mapper -> { + SimpleModule module = new SimpleModule(); + module.addSerializer(MetricResponse.class, new MetricResponseSerializer()); + mapper.registerModule(module); + })); + }).get("/metrics", ctx -> ctx.json(metrics.getAll())) + .get("/metrics/groups", ctx -> ctx.json(metrics.getResourceGroups())) + .get("/metrics/manager", ctx -> ctx.json(metrics.getManager())) + .get("/metrics/gc", ctx -> ctx.json(metrics.getGarbageCollector())) + .get("/metrics/compactors/{group}", + ctx -> ctx.json(metrics.getCompactors(ctx.pathParam("group")))) + .get("/metrics/sservers/{group}", + ctx -> ctx.json(metrics.getSServers(ctx.pathParam("group")))) + .get("/metrics/tservers/{group}", + ctx -> ctx.json(metrics.getTServers(ctx.pathParam("group")))) + .start(hostname, port); + + LOG.info("New Monitor listening on {}", port); + } +} diff --git a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java index 6319762f10e..63afc866926 100644 --- a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java +++ b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsThriftRpcIT.java @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.accumulo.test.metrics; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -56,13 +74,13 @@ protected void configure(MiniAccumuloConfigImpl cfg, Configuration hadoopCoreSit cfg.getClusterServerConfiguration().setNumDefaultTabletServers(1); cfg.getClusterServerConfiguration().addCompactorResourceGroup("CTEST", 3); cfg.getClusterServerConfiguration().addScanServerResourceGroup("STEST", 2); - cfg.getClusterServerConfiguration().addTabletServerResourceGroup("TTEST", 1); + cfg.getClusterServerConfiguration().addTabletServerResourceGroup("TTEST", 1); } - + private int handleMetrics(final MetricResponse response) { if (response.getMetricsSize() == 0) { - System.out.println("type: " + response.getServerType() + ", host: " + response.getServer() + - ", group: " + response.getResourceGroup() + "has no metrics"); + System.out.println("type: " + response.getServerType() + ", host: " + response.getServer() + + ", group: " + response.getResourceGroup() + "has no metrics"); return response.getMetricsSize(); } for (final ByteBuffer binary : response.getMetrics()) { @@ -72,14 +90,14 @@ private int handleMetrics(final MetricResponse response) { FTag t = fm.tags(i); tags.add(t.key() + " = " + t.value()); } - System.out.println("type: " + response.getServerType() + ", host: " + response.getServer() + - ", group: " + response.getResourceGroup() + ", time: " + response.getTimestamp() + - ", name: " + fm.name() + ", type: " + fm.type() + ", tags: " + tags + - ", dval: " + fm.dvalue() + ", ival: " + fm.ivalue() + ", lval: " + fm.lvalue()); + System.out.println("type: " + response.getServerType() + ", host: " + response.getServer() + + ", group: " + response.getResourceGroup() + ", time: " + response.getTimestamp() + + ", name: " + fm.name() + ", type: " + fm.type() + ", tags: " + tags + ", dval: " + + fm.dvalue() + ", ival: " + fm.ivalue() + ", lval: " + fm.lvalue()); } return response.getMetricsSize(); } - + @Test public void testRpc() throws Exception { try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { @@ -90,12 +108,14 @@ public void testRpc() throws Exception { Set managers = client.instanceOperations().getServers(ServerId.Type.MANAGER); assertEquals(1, managers.size()); for (ServerId server : managers) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, + HostAndPort.fromParts(server.getHost(), server.getPort()), cc); try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - assertEquals(server.getResourceGroup(), response.getResourceGroup()); - assertEquals(MetricSource.MANAGER, response.getServerType()); - assertTrue(handleMetrics(response) > 0); + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), + getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.MANAGER, response.getServerType()); + assertTrue(handleMetrics(response) > 0); } finally { ThriftUtil.returnClient(metricsClient, cc); } @@ -123,16 +143,18 @@ public void testRpc() throws Exception { } else { fail("Garbage Collector not found in ZooKeeper"); } - + Set compactors = client.instanceOperations().getServers(ServerId.Type.COMPACTOR); assertEquals(4, compactors.size()); for (ServerId server : compactors) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, + HostAndPort.fromParts(server.getHost(), server.getPort()), cc); try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - assertEquals(server.getResourceGroup(), response.getResourceGroup()); - assertEquals(MetricSource.COMPACTOR, response.getServerType()); - assertTrue(handleMetrics(response) > 0); + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), + getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.COMPACTOR, response.getServerType()); + assertTrue(handleMetrics(response) > 0); } finally { ThriftUtil.returnClient(metricsClient, cc); } @@ -140,12 +162,14 @@ public void testRpc() throws Exception { Set sservers = client.instanceOperations().getServers(ServerId.Type.SCAN_SERVER); assertEquals(3, sservers.size()); for (ServerId server : sservers) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, + HostAndPort.fromParts(server.getHost(), server.getPort()), cc); try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - assertEquals(server.getResourceGroup(), response.getResourceGroup()); - assertEquals(MetricSource.SCAN_SERVER, response.getServerType()); - assertTrue(handleMetrics(response) > 0); + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), + getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.SCAN_SERVER, response.getServerType()); + assertTrue(handleMetrics(response) > 0); } finally { ThriftUtil.returnClient(metricsClient, cc); } @@ -153,12 +177,14 @@ public void testRpc() throws Exception { Set tservers = client.instanceOperations().getServers(ServerId.Type.TABLET_SERVER); assertEquals(2, tservers.size()); for (ServerId server : tservers) { - Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, HostAndPort.fromParts(server.getHost(), server.getPort()), cc); + Client metricsClient = ThriftUtil.getClient(ThriftClientTypes.METRICS, + HostAndPort.fromParts(server.getHost(), server.getPort()), cc); try { - MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), getCluster().getServerContext().rpcCreds()); - assertEquals(server.getResourceGroup(), response.getResourceGroup()); - assertEquals(MetricSource.TABLET_SERVER, response.getServerType()); - assertTrue(handleMetrics(response) > 0); + MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), + getCluster().getServerContext().rpcCreds()); + assertEquals(server.getResourceGroup(), response.getResourceGroup()); + assertEquals(MetricSource.TABLET_SERVER, response.getServerType()); + assertTrue(handleMetrics(response) > 0); } finally { ThriftUtil.returnClient(metricsClient, cc); } From f01a636ef0781a903e9d7958150e44effe953ac0 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Wed, 23 Oct 2024 22:07:35 +0000 Subject: [PATCH 04/22] Remove unused depedency --- server/monitor/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index 28be36533dd..f67d88fdda6 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -142,10 +142,6 @@ org.eclipse.jetty jetty-util - - org.eclipse.jetty.websocket - websocket-jetty-server - org.glassfish.hk2 hk2-api From 7cf7d9666f821ae89dfe4237dc4164b7d0eaabf5 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Thu, 24 Oct 2024 18:29:09 +0000 Subject: [PATCH 05/22] Made port static at 43331, added TLS like old monitor and test --- .../apache/accumulo/monitor/NewMonitor.java | 71 +++++++- .../test/functional/NewMonitorSslIT.java | 158 ++++++++++++++++++ 2 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java index e677049c22e..79261cf2444 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java @@ -22,12 +22,19 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; +import java.util.EnumSet; +import org.apache.accumulo.core.conf.AccumuloConfiguration; +import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.metrics.flatbuffers.FMetric; import org.apache.accumulo.core.metrics.flatbuffers.FTag; import org.apache.accumulo.core.metrics.thrift.MetricResponse; import org.apache.accumulo.core.util.threads.Threads; import org.apache.accumulo.server.ServerContext; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,12 +94,21 @@ public void serialize(MetricResponse value, JsonGenerator gen, SerializerProvide private static final Logger LOG = LoggerFactory.getLogger(NewMonitor.class); + private static final EnumSet requireForSecure = + EnumSet.of(Property.MONITOR_SSL_KEYSTORE, Property.MONITOR_SSL_KEYSTOREPASS, + Property.MONITOR_SSL_TRUSTSTORE, Property.MONITOR_SSL_TRUSTSTOREPASS); + + public static final int NEW_MONITOR_PORT = 43331; + private final ServerContext ctx; private final String hostname; + private final boolean secure; public NewMonitor(ServerContext ctx, String hostname) { this.ctx = ctx; this.hostname = hostname; + secure = requireForSecure.stream().map(ctx.getConfiguration()::get) + .allMatch(s -> s != null && !s.isEmpty()); } @SuppressFBWarnings(value = "UNENCRYPTED_SERVER_SOCKET", @@ -106,9 +122,9 @@ public void start() throws IOException { // Find a free socket ServerSocket ss = new ServerSocket(); ss.setReuseAddress(true); - ss.bind(new InetSocketAddress(hostname, 0)); + ss.bind(new InetSocketAddress(hostname, NEW_MONITOR_PORT)); ss.close(); - final int port = ss.getLocalPort(); + final int httpPort = ss.getLocalPort(); Javalin.create(config -> { config.bundledPlugins.enableDevLogging(); @@ -118,6 +134,53 @@ public void start() throws IOException { module.addSerializer(MetricResponse.class, new MetricResponseSerializer()); mapper.registerModule(module); })); + + if (secure) { + LOG.debug("Configuring Jetty to use TLS"); + + final AccumuloConfiguration conf = ctx.getConfiguration(); + + final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + // If the key password is the same as the keystore password, we don't + // have to explicitly set it. Thus, if the user doesn't provide a key + // password, don't set anything. + final String keyPass = conf.get(Property.MONITOR_SSL_KEYPASS); + if (!Property.MONITOR_SSL_KEYPASS.getDefaultValue().equals(keyPass)) { + sslContextFactory.setKeyManagerPassword(keyPass); + } + sslContextFactory.setKeyStorePath(conf.get(Property.MONITOR_SSL_KEYSTORE)); + sslContextFactory.setKeyStorePassword(conf.get(Property.MONITOR_SSL_KEYSTOREPASS)); + sslContextFactory.setKeyStoreType(conf.get(Property.MONITOR_SSL_KEYSTORETYPE)); + sslContextFactory.setTrustStorePath(conf.get(Property.MONITOR_SSL_TRUSTSTORE)); + sslContextFactory.setTrustStorePassword(conf.get(Property.MONITOR_SSL_TRUSTSTOREPASS)); + sslContextFactory.setTrustStoreType(conf.get(Property.MONITOR_SSL_TRUSTSTORETYPE)); + + final String includedCiphers = conf.get(Property.MONITOR_SSL_INCLUDE_CIPHERS); + if (!Property.MONITOR_SSL_INCLUDE_CIPHERS.getDefaultValue().equals(includedCiphers)) { + sslContextFactory.setIncludeCipherSuites(includedCiphers.split(",")); + } + + final String excludedCiphers = conf.get(Property.MONITOR_SSL_EXCLUDE_CIPHERS); + if (!Property.MONITOR_SSL_EXCLUDE_CIPHERS.getDefaultValue().equals(excludedCiphers)) { + sslContextFactory.setExcludeCipherSuites(excludedCiphers.split(",")); + } + + final String includeProtocols = conf.get(Property.MONITOR_SSL_INCLUDE_PROTOCOLS); + if (includeProtocols != null && !includeProtocols.isEmpty()) { + sslContextFactory.setIncludeProtocols(includeProtocols.split(",")); + } + + HttpConnectionFactory httpFactory = new HttpConnectionFactory(); + SslConnectionFactory sslFactory = + new SslConnectionFactory(sslContextFactory, httpFactory.getProtocol()); + config.jetty.addConnector((s, httpConfig) -> { + ServerConnector conn = new ServerConnector(s, sslFactory, httpFactory); + conn.setHost(hostname); + conn.setPort(httpPort); + return conn; + }); + } + }).get("/metrics", ctx -> ctx.json(metrics.getAll())) .get("/metrics/groups", ctx -> ctx.json(metrics.getResourceGroups())) .get("/metrics/manager", ctx -> ctx.json(metrics.getManager())) @@ -128,8 +191,8 @@ public void start() throws IOException { ctx -> ctx.json(metrics.getSServers(ctx.pathParam("group")))) .get("/metrics/tservers/{group}", ctx -> ctx.json(metrics.getTServers(ctx.pathParam("group")))) - .start(hostname, port); + .start(hostname, httpPort); - LOG.info("New Monitor listening on {}", port); + LOG.info("New Monitor listening on port: {}", httpPort); } } diff --git a/test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java b/test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java new file mode 100644 index 00000000000..75e175a76ab --- /dev/null +++ b/test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.test.functional; + +import static org.apache.accumulo.core.util.LazySingletons.RANDOM; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.accumulo.core.client.Accumulo; +import org.apache.accumulo.core.client.AccumuloClient; +import org.apache.accumulo.core.clientImpl.ClientContext; +import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.util.MonitorUtil; +import org.apache.accumulo.minicluster.ServerType; +import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl; +import org.apache.accumulo.monitor.NewMonitor; +import org.apache.hadoop.conf.Configuration; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Check SSL for the Monitor + */ +public class NewMonitorSslIT extends ConfigurableMacBase { + + @Override + protected Duration defaultTimeout() { + return Duration.ofMinutes(6); + } + + @BeforeAll + public static void initHttps() throws NoSuchAlgorithmException, KeyManagementException { + SSLContext ctx = SSLContext.getInstance("TLSv1.3"); + TrustManager[] tm = {new TestTrustManager()}; + ctx.init(new KeyManager[0], tm, RANDOM.get()); + SSLContext.setDefault(ctx); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); + } + + @SuppressFBWarnings(value = "WEAK_TRUST_MANAGER", + justification = "trust manager is okay for testing") + private static class TestTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) {} + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) {} + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + } + + @SuppressFBWarnings(value = "WEAK_HOSTNAME_VERIFIER", justification = "okay for test") + private static class TestHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } + + @Override + public void configure(MiniAccumuloConfigImpl cfg, Configuration hadoopCoreSite) { + super.configure(cfg, hadoopCoreSite); + File baseDir = createTestDir(this.getClass().getName() + "_" + this.testName()); + configureForSsl(cfg, getSslDir(baseDir)); + Map siteConfig = cfg.getSiteConfig(); + siteConfig.put(Property.MONITOR_SSL_KEYSTORE.getKey(), + siteConfig.get(Property.RPC_SSL_KEYSTORE_PATH.getKey())); + siteConfig.put(Property.MONITOR_SSL_KEYSTOREPASS.getKey(), + siteConfig.get(Property.RPC_SSL_KEYSTORE_PASSWORD.getKey())); + if (siteConfig.containsKey(Property.RPC_SSL_KEYSTORE_TYPE.getKey())) { + siteConfig.put(Property.MONITOR_SSL_KEYSTORETYPE.getKey(), + siteConfig.get(Property.RPC_SSL_KEYSTORE_TYPE.getKey())); + } else { + siteConfig.put(Property.MONITOR_SSL_KEYSTORETYPE.getKey(), + Property.RPC_SSL_KEYSTORE_TYPE.getDefaultValue()); + } + siteConfig.put(Property.MONITOR_SSL_TRUSTSTORE.getKey(), + siteConfig.get(Property.RPC_SSL_TRUSTSTORE_PATH.getKey())); + siteConfig.put(Property.MONITOR_SSL_TRUSTSTOREPASS.getKey(), + siteConfig.get(Property.RPC_SSL_TRUSTSTORE_PASSWORD.getKey())); + if (siteConfig.containsKey(Property.RPC_SSL_TRUSTSTORE_TYPE.getKey())) { + siteConfig.put(Property.MONITOR_SSL_TRUSTSTORETYPE.getKey(), + siteConfig.get(Property.RPC_SSL_TRUSTSTORE_TYPE.getKey())); + } else { + siteConfig.put(Property.MONITOR_SSL_TRUSTSTORETYPE.getKey(), + Property.RPC_SSL_TRUSTSTORE_TYPE.getDefaultValue()); + } + cfg.setSiteConfig(siteConfig); + } + + @SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "url provided by test") + @Test + public void test() throws Exception { + log.debug("Starting Monitor"); + cluster.getClusterControl().startAllServers(ServerType.MONITOR); + String monitorLocation = null; + // wait for Monitor to start + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + while (monitorLocation == null) { + try { + monitorLocation = MonitorUtil.getLocation((ClientContext) client); + } catch (Exception e) { + // ignored + } + if (monitorLocation == null) { + log.debug("Could not fetch monitor HTTP address from zookeeper"); + Thread.sleep(2000); + } + } + } + // replace port and path + monitorLocation = monitorLocation.substring(0, monitorLocation.lastIndexOf(':') + 1) + + NewMonitor.NEW_MONITOR_PORT + "/routes"; + URL url = new URL(monitorLocation); + log.debug("Fetching web page {}", url); + String result = FunctionalTestUtils.readWebPage(url).body(); + assertTrue(result.length() > 100); + assertTrue(result.indexOf("/metrics") >= 0); + } + +} From 0543a4885f718a999740844c4e81bc7a2ccb2d43 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Thu, 24 Oct 2024 18:53:22 +0000 Subject: [PATCH 06/22] Added endpoint for reporting problem hosts --- .../main/java/org/apache/accumulo/monitor/MetricsFetcher.java | 4 ++++ .../src/main/java/org/apache/accumulo/monitor/NewMonitor.java | 1 + 2 files changed, 5 insertions(+) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java index d86c4cad327..0a47b288260 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java @@ -244,6 +244,10 @@ public Set getResourceGroups() { return groups; } + public Collection getProblemHosts() { + return problemHosts; + } + public Collection getAll() { return allMetrics.asMap().values(); } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java index 79261cf2444..9ef89c0be3a 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java @@ -191,6 +191,7 @@ public void start() throws IOException { ctx -> ctx.json(metrics.getSServers(ctx.pathParam("group")))) .get("/metrics/tservers/{group}", ctx -> ctx.json(metrics.getTServers(ctx.pathParam("group")))) + .get("/metrics/problems", ctx -> ctx.json(metrics.getProblemHosts())) .start(hostname, httpPort); LOG.info("New Monitor listening on port: {}", httpPort); From 411f0743bf51dc07321a0d9a38f443eb380bc0d2 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Fri, 25 Oct 2024 20:30:18 +0000 Subject: [PATCH 07/22] Added compactions, fetch delay --- .../accumulo/monitor/MetricsFetcher.java | 108 +++++++++++++++++- .../apache/accumulo/monitor/NewMonitor.java | 89 +++++++++++++-- 2 files changed, 182 insertions(+), 15 deletions(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java index 0a47b288260..e81bbdb5ab5 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java @@ -26,16 +26,24 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.compaction.thrift.CompactionCoordinatorService; +import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; +import org.apache.accumulo.core.compaction.thrift.TExternalCompactionList; import org.apache.accumulo.core.lock.ServiceLockData; import org.apache.accumulo.core.lock.ServiceLockData.ThriftService; import org.apache.accumulo.core.lock.ServiceLockPaths.ServiceLockPath; @@ -49,7 +57,9 @@ import org.apache.accumulo.core.util.threads.ThreadPools.ExecutionError; import org.apache.accumulo.server.ServerContext; import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransportException; import org.checkerframework.checker.nullness.qual.Nullable; +import org.eclipse.jetty.util.NanoTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -134,6 +144,62 @@ public void run() { } + private class CompactionListFetcher implements Runnable { + + private final String coordinatorMissingMsg = + "Error getting the compaction coordinator client. Check that the Manager is running."; + + // Copied from Monitor + private TExternalCompactionList getExternalCompactions() { + Set managers = ctx.instanceOperations().getServers(ServerId.Type.MANAGER); + if (managers.isEmpty()) { + throw new IllegalStateException(coordinatorMissingMsg); + } + ServerId manager = managers.iterator().next(); + HostAndPort hp = HostAndPort.fromParts(manager.getHost(), manager.getPort()); + try { + CompactionCoordinatorService.Client client = + ThriftUtil.getClient(ThriftClientTypes.COORDINATOR, hp, ctx); + try { + return client.getRunningCompactions(TraceUtil.traceInfo(), ctx.rpcCreds()); + } catch (Exception e) { + throw new IllegalStateException("Unable to get running compactions from " + hp, e); + } finally { + if (client != null) { + ThriftUtil.returnClient(client, ctx); + } + } + } catch (TTransportException e) { + LOG.error("Unable to get Compaction coordinator at {}", hp); + throw new IllegalStateException(coordinatorMissingMsg, e); + } + } + + @Override + public void run() { + try { + TExternalCompactionList running = getExternalCompactions(); + // Create an index into the running compaction list that is + // sorted by duration, meaning earliest start time first. This + // will allow us to show topN longest running compactions. + Map timeSortedEcids = new TreeMap<>(); + if (running.getCompactions() != null) { + running.getCompactions().forEach((ecid, extComp) -> { + if (extComp.getUpdates() != null && !extComp.getUpdates().isEmpty()) { + Set orderedUpdateTimes = new TreeSet<>(extComp.getUpdates().keySet()); + timeSortedEcids.put(orderedUpdateTimes.iterator().next(), ecid); + } + }); + } + runningCompactions.set(running.getCompactions()); + runningCompactionsDurationIndex.set(timeSortedEcids); + } catch (Exception e) { + LOG.error("Error gathering running compaction information", e); + } + } + + } + private static final Set EMPTY_MUTABLE_SET = new HashSet<>(); private final String poolName = "MonitorMetricsThreadPool"; @@ -141,6 +207,8 @@ public void run() { .getPoolBuilder(poolName).numCoreThreads(10).withTimeOut(30, SECONDS).build(); private final ServerContext ctx; + private final Supplier connectionCount; + private final AtomicBoolean newConnectionEvent = new AtomicBoolean(false); private final Cache allMetrics; private final Set problemHosts = new HashSet<>(); private final AtomicReference manager = new AtomicReference<>(); @@ -148,13 +216,22 @@ public void run() { private final ConcurrentHashMap> compactors = new ConcurrentHashMap<>(); private final ConcurrentHashMap> sservers = new ConcurrentHashMap<>(); private final ConcurrentHashMap> tservers = new ConcurrentHashMap<>(); + private final AtomicReference> runningCompactions = + new AtomicReference<>(); + private final AtomicReference> runningCompactionsDurationIndex = + new AtomicReference<>(); - public MetricsFetcher(ServerContext ctx) { + public MetricsFetcher(ServerContext ctx, Supplier connectionCount) { this.ctx = ctx; + this.connectionCount = connectionCount; this.allMetrics = Caffeine.newBuilder().executor(pool).scheduler(Scheduler.systemScheduler()) .expireAfterWrite(Duration.ofMinutes(10)).removalListener(this::onRemoval).build(); } + public void newConnectionEvent() { + this.newConnectionEvent.compareAndSet(false, true); + } + @Override public void onRemoval(@Nullable ServerId server, @Nullable MetricResponse response, RemovalCause cause) { @@ -187,8 +264,22 @@ public void onRemoval(@Nullable ServerId server, @Nullable MetricResponse respon @Override public void run() { + long refreshTime = 0; + while (true) { + // Don't fetch new data if there are no connections + // On an initial connection, no data may be displayed + // If a connection has not been made in a while, stale data may be displayed + // Only refresh every 5s (old monitor logic) + while (!newConnectionEvent.get() && connectionCount.get() == 0 + && NanoTime.millisElapsed(refreshTime, NanoTime.now()) > 5000) { + Thread.onSpinWait(); + } + // reset the connection event flag + newConnectionEvent.compareAndExchange(true, false); + + LOG.info("Fetching metrics from servers"); final List> futures = new ArrayList<>(); for (ServerId.Type type : ServerId.Type.values()) { @@ -211,6 +302,9 @@ public void run() { } } + // Fetch external compaction information from the Manager + futures.add(this.pool.submit(new CompactionListFetcher())); + while (futures.size() > 0) { Iterator> iter = futures.iterator(); while (iter.hasNext()) { @@ -229,8 +323,7 @@ public void run() { UtilWaitThread.sleep(3_000); } } - - UtilWaitThread.sleep(30_000); + refreshTime = NanoTime.now(); } } @@ -289,4 +382,13 @@ public Collection getTServers(String resourceGroup) { return allMetrics.getAllPresent(tservers.get(resourceGroup)).values(); } + public Collection getCompactions(int topN) { + List results = new ArrayList<>(); + Map compactions = runningCompactions.get(); + Iterator ecids = runningCompactionsDurationIndex.get().values().iterator(); + for (int i = 0; i < topN && ecids.hasNext(); i++) { + results.add(compactions.get(ecids.next())); + } + return results; + } } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java index 9ef89c0be3a..8b97c83a38e 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java @@ -24,13 +24,21 @@ import java.nio.ByteBuffer; import java.util.EnumSet; +import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.metrics.flatbuffers.FMetric; import org.apache.accumulo.core.metrics.flatbuffers.FTag; import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.core.tabletserver.thrift.TExternalCompactionJob; import org.apache.accumulo.core.util.threads.Threads; import org.apache.accumulo.server.ServerContext; +import org.apache.thrift.TBase; +import org.apache.thrift.TException; +import org.apache.thrift.TSerializer; +import org.apache.thrift.protocol.TSimpleJSONProtocol; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.ConnectionStatistics; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; @@ -48,7 +56,24 @@ import io.javalin.json.JavalinJackson; import io.javalin.security.RouteRole; -public class NewMonitor { +public class NewMonitor implements Connection.Listener { + + public static class ThriftSerializer extends JsonSerializer> { + + private final TSimpleJSONProtocol.Factory factory = new TSimpleJSONProtocol.Factory(); + + @Override + public void serialize(TBase value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + try { + // TSerializer is likely not thread safe + gen.writeRaw(new TSerializer(factory).toString(value)); + } catch (TException e) { + LOG.error("Error serializing Thrift object", e); + } + } + + } public static class MetricResponseSerializer extends JsonSerializer { @@ -103,22 +128,23 @@ public void serialize(MetricResponse value, JsonGenerator gen, SerializerProvide private final ServerContext ctx; private final String hostname; private final boolean secure; + private final ConnectionStatistics connStats; + private final MetricsFetcher metrics; public NewMonitor(ServerContext ctx, String hostname) { this.ctx = ctx; this.hostname = hostname; - secure = requireForSecure.stream().map(ctx.getConfiguration()::get) + this.secure = requireForSecure.stream().map(ctx.getConfiguration()::get) .allMatch(s -> s != null && !s.isEmpty()); + + this.connStats = new ConnectionStatistics(); + this.metrics = new MetricsFetcher(ctx, connStats::getConnections); } @SuppressFBWarnings(value = "UNENCRYPTED_SERVER_SOCKET", justification = "TODO Replace before merging") public void start() throws IOException { - MetricsFetcher metrics = new MetricsFetcher(ctx); - - Threads.createThread("Metric Fetcher Thread", metrics).start(); - // Find a free socket ServerSocket ss = new ServerSocket(); ss.setReuseAddress(true); @@ -126,15 +152,25 @@ public void start() throws IOException { ss.close(); final int httpPort = ss.getLocalPort(); + Threads.createThread("Metric Fetcher Thread", metrics).start(); + Javalin.create(config -> { + // TODO Make dev logging and route overview configurable based on property + // They are useful for development and debugging, but should probably not + // be enabled for normal use. config.bundledPlugins.enableDevLogging(); config.bundledPlugins.enableRouteOverview("/routes", new RouteRole[] {}); config.jsonMapper(new JavalinJackson().updateMapper(mapper -> { SimpleModule module = new SimpleModule(); module.addSerializer(MetricResponse.class, new MetricResponseSerializer()); + module.addSerializer(TExternalCompaction.class, new ThriftSerializer()); + module.addSerializer(TExternalCompactionJob.class, new ThriftSerializer()); mapper.registerModule(module); })); + final HttpConnectionFactory httpFactory = new HttpConnectionFactory(); + + // Set up TLS if (secure) { LOG.debug("Configuring Jetty to use TLS"); @@ -170,18 +206,32 @@ public void start() throws IOException { sslContextFactory.setIncludeProtocols(includeProtocols.split(",")); } - HttpConnectionFactory httpFactory = new HttpConnectionFactory(); - SslConnectionFactory sslFactory = + final SslConnectionFactory sslFactory = new SslConnectionFactory(sslContextFactory, httpFactory.getProtocol()); config.jetty.addConnector((s, httpConfig) -> { ServerConnector conn = new ServerConnector(s, sslFactory, httpFactory); + // Capture connection statistics + conn.addBean(connStats); + // Listen for connection events + conn.addBean(this); conn.setHost(hostname); conn.setPort(httpPort); return conn; }); - } - - }).get("/metrics", ctx -> ctx.json(metrics.getAll())) + } else { + config.jetty.addConnector((s, httpConfig) -> { + ServerConnector conn = new ServerConnector(s, httpFactory); + // Capture connection statistics + conn.addBean(connStats); + // Listen for connection events + conn.addBean(this); + conn.setHost(hostname); + conn.setPort(httpPort); + return conn; + }); + } + }).get("/stats", ctx -> ctx.result(connStats.dump())) + .get("/metrics", ctx -> ctx.json(metrics.getAll())) .get("/metrics/groups", ctx -> ctx.json(metrics.getResourceGroups())) .get("/metrics/manager", ctx -> ctx.json(metrics.getManager())) .get("/metrics/gc", ctx -> ctx.json(metrics.getGarbageCollector())) @@ -192,8 +242,23 @@ public void start() throws IOException { .get("/metrics/tservers/{group}", ctx -> ctx.json(metrics.getTServers(ctx.pathParam("group")))) .get("/metrics/problems", ctx -> ctx.json(metrics.getProblemHosts())) - .start(hostname, httpPort); + .get("/metrics/compactions", ctx -> ctx.json(metrics.getCompactions(25))) + .get("/metrics/compactions/{num}", + ctx -> ctx.json(metrics.getCompactions(Integer.parseInt(ctx.pathParam("num"))))) + .start(); LOG.info("New Monitor listening on port: {}", httpPort); } + + @Override + public void onOpened(Connection connection) { + LOG.info("New connection event"); + metrics.newConnectionEvent(); + } + + @Override + public void onClosed(Connection connection) { + // do nothing + } + } From d83f1004ebd9235001a9daa22f3018fd033a61a6 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Mon, 28 Oct 2024 13:55:01 +0000 Subject: [PATCH 08/22] Fix formatting --- .../src/main/java/org/apache/accumulo/monitor/NewMonitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java index 8b97c83a38e..8c3cb9caf11 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java @@ -229,7 +229,7 @@ public void start() throws IOException { conn.setPort(httpPort); return conn; }); - } + } }).get("/stats", ctx -> ctx.result(connStats.dump())) .get("/metrics", ctx -> ctx.json(metrics.getAll())) .get("/metrics/groups", ctx -> ctx.json(metrics.getResourceGroups())) From 37be1923dcc2115689a226035ecc1c2d197f87ae Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Mon, 28 Oct 2024 14:29:41 +0000 Subject: [PATCH 09/22] Fixed missing import --- server/monitor/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index f67d88fdda6..727bbdf8776 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -130,6 +130,10 @@ org.checkerframework checker-qual + + org.eclipse.jetty + jetty-io + org.eclipse.jetty jetty-server From 6b248150fa653e9ec1ac01ac508fc591ea3f3c27 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Mon, 28 Oct 2024 17:42:14 +0000 Subject: [PATCH 10/22] Add logging --- .../org/apache/accumulo/monitor/MetricsFetcher.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java index e81bbdb5ab5..83bf670f327 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java @@ -124,8 +124,8 @@ public void run() { .add(server); break; default: + LOG.error("Unhandled server type in fetch metric response: {}", response.serverType); break; - } } finally { ThriftUtil.returnClient(metricsClient, ctx); @@ -225,7 +225,7 @@ public MetricsFetcher(ServerContext ctx, Supplier connectionCount) { this.ctx = ctx; this.connectionCount = connectionCount; this.allMetrics = Caffeine.newBuilder().executor(pool).scheduler(Scheduler.systemScheduler()) - .expireAfterWrite(Duration.ofMinutes(10)).removalListener(this::onRemoval).build(); + .expireAfterWrite(Duration.ofMinutes(10)).evictionListener(this::onRemoval).build(); } public void newConnectionEvent() { @@ -238,6 +238,7 @@ public void onRemoval(@Nullable ServerId server, @Nullable MetricResponse respon if (server == null) { return; } + LOG.info("{} has been evicted", server); if (server instanceof GcServerId) { gc.compareAndSet(server, null); } else { @@ -324,8 +325,15 @@ public void run() { } } refreshTime = NanoTime.now(); + LOG.info("Finished fetching metrics from servers"); + LOG.info("All: {}, Manager: {}, Garbage Collector: {}, Compactors: {}, Scan Servers: {}, Tablet Servers: {}", + allMetrics.estimatedSize(), manager.get() != null, gc.get() != null, + compactors.values().stream().mapToInt(s -> s.size()).sum(), + sservers.values().stream().mapToInt(s -> s.size()).sum(), + tservers.values().stream().mapToInt(s -> s.size()).sum()); } + } public Set getResourceGroups() { From 78f7367113c296213f1b6f79746e64d2635fbfe6 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Mon, 28 Oct 2024 17:50:37 +0000 Subject: [PATCH 11/22] Formatting --- .../main/java/org/apache/accumulo/monitor/MetricsFetcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java index 83bf670f327..91e64d11430 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java @@ -326,14 +326,14 @@ public void run() { } refreshTime = NanoTime.now(); LOG.info("Finished fetching metrics from servers"); - LOG.info("All: {}, Manager: {}, Garbage Collector: {}, Compactors: {}, Scan Servers: {}, Tablet Servers: {}", + LOG.info( + "All: {}, Manager: {}, Garbage Collector: {}, Compactors: {}, Scan Servers: {}, Tablet Servers: {}", allMetrics.estimatedSize(), manager.get() != null, gc.get() != null, compactors.values().stream().mapToInt(s -> s.size()).sum(), sservers.values().stream().mapToInt(s -> s.size()).sum(), tservers.values().stream().mapToInt(s -> s.size()).sum()); } - } public Set getResourceGroups() { From 9b3c10ae8aae329216bc64202fb4530564fd63f0 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Tue, 29 Oct 2024 19:56:15 +0000 Subject: [PATCH 12/22] Refactored metrics collection, added new paths, needs testing --- server/monitor/pom.xml | 4 + .../accumulo/monitor/MetricsFetcher.java | 220 ++++++++++-------- .../apache/accumulo/monitor/NewMonitor.java | 23 +- .../accumulo/monitor/ResponseSummary.java | 211 +++++++++++++++++ 4 files changed, 361 insertions(+), 97 deletions(-) create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/ResponseSummary.java diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index 727bbdf8776..a8453fa665b 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -74,6 +74,10 @@ + + io.micrometer + micrometer-core + jakarta.inject jakarta.inject-api diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java index 91e64d11430..a02a5dd7cb8 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java @@ -23,7 +23,6 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -31,15 +30,14 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.stream.Collectors; -import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.admin.servers.ServerId; import org.apache.accumulo.core.compaction.thrift.CompactionCoordinatorService; import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; @@ -70,6 +68,8 @@ import com.github.benmanes.caffeine.cache.Scheduler; import com.google.common.net.HostAndPort; +import io.micrometer.core.instrument.DistributionSummary; + public class MetricsFetcher implements RemovalListener, Runnable { private static final Logger LOG = LoggerFactory.getLogger(MetricsFetcher.class); @@ -85,10 +85,12 @@ private class MetricFetcher implements Runnable { private final ServerContext ctx; private final ServerId server; + private final ResponseSummary summary; - private MetricFetcher(ServerContext ctx, ServerId server) { + private MetricFetcher(ServerContext ctx, ServerId server, ResponseSummary summary) { this.ctx = ctx; this.server = server; + this.summary = summary; } @Override @@ -98,47 +100,13 @@ public void run() { HostAndPort.fromParts(server.getHost(), server.getPort()), ctx); try { MetricResponse response = metricsClient.getMetrics(TraceUtil.traceInfo(), ctx.rpcCreds()); - problemHosts.remove(server); - allMetrics.put(server, response); - switch (response.serverType) { - case COMPACTOR: - compactors.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()) - .add(server); - break; - case GARBAGE_COLLECTOR: - if (gc.get() == null || !gc.get().equals(server)) { - gc.set(server); - } - break; - case MANAGER: - if (manager.get() == null || !manager.get().equals(server)) { - manager.set(server); - } - break; - case SCAN_SERVER: - sservers.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()) - .add(server); - break; - case TABLET_SERVER: - tservers.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()) - .add(server); - break; - default: - LOG.error("Unhandled server type in fetch metric response: {}", response.serverType); - break; - } + summary.processResponse(server, response); } finally { ThriftUtil.returnClient(metricsClient, ctx); } } catch (TException e) { LOG.warn("Error trying to get metrics from server: {}", server); - // Error talking to server, add to problem hosts and remove from everything else - problemHosts.add(server); - gc.compareAndSet(server, null); - manager.compareAndSet(server, null); - compactors.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); - sservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); - tservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); + summary.processError(server); } } @@ -200,8 +168,6 @@ public void run() { } - private static final Set EMPTY_MUTABLE_SET = new HashSet<>(); - private final String poolName = "MonitorMetricsThreadPool"; private final ThreadPoolExecutor pool = ThreadPools.getServerThreadPools() .getPoolBuilder(poolName).numCoreThreads(10).withTimeOut(30, SECONDS).build(); @@ -210,12 +176,9 @@ public void run() { private final Supplier connectionCount; private final AtomicBoolean newConnectionEvent = new AtomicBoolean(false); private final Cache allMetrics; - private final Set problemHosts = new HashSet<>(); - private final AtomicReference manager = new AtomicReference<>(); - private final AtomicReference gc = new AtomicReference<>(); - private final ConcurrentHashMap> compactors = new ConcurrentHashMap<>(); - private final ConcurrentHashMap> sservers = new ConcurrentHashMap<>(); - private final ConcurrentHashMap> tservers = new ConcurrentHashMap<>(); + + private final AtomicReference summaryRef = new AtomicReference<>(); + private final AtomicReference> runningCompactions = new AtomicReference<>(); private final AtomicReference> runningCompactionsDurationIndex = @@ -239,27 +202,7 @@ public void onRemoval(@Nullable ServerId server, @Nullable MetricResponse respon return; } LOG.info("{} has been evicted", server); - if (server instanceof GcServerId) { - gc.compareAndSet(server, null); - } else { - switch (server.getType()) { - case COMPACTOR: - compactors.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); - break; - case MANAGER: - manager.compareAndSet(server, null); - break; - case SCAN_SERVER: - sservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); - break; - case TABLET_SERVER: - tservers.getOrDefault(server.getResourceGroup(), EMPTY_MUTABLE_SET).remove(server); - break; - default: - LOG.error("Unhandled server type sent to onRemoval: {}", server); - break; - } - } + getSummary().processError(server); } @Override @@ -281,11 +224,13 @@ public void run() { newConnectionEvent.compareAndExchange(true, false); LOG.info("Fetching metrics from servers"); + final List> futures = new ArrayList<>(); + final ResponseSummary summary = new ResponseSummary(allMetrics); for (ServerId.Type type : ServerId.Type.values()) { for (ServerId server : this.ctx.instanceOperations().getServers(type)) { - futures.add(this.pool.submit(new MetricFetcher(this.ctx, server))); + futures.add(this.pool.submit(new MetricFetcher(this.ctx, server, summary))); } } ThreadPools.resizePool(pool, () -> Math.max(20, (futures.size() / 20)), poolName); @@ -299,7 +244,7 @@ public void run() { String resourceGroup = sld.orElseThrow().getGroup(ThriftService.GC); HostAndPort hp = HostAndPort.fromString(location); futures.add(this.pool.submit(new MetricFetcher(this.ctx, - new GcServerId(resourceGroup, hp.getHost(), hp.getPort())))); + new GcServerId(resourceGroup, hp.getHost(), hp.getPort()), summary))); } } @@ -328,25 +273,32 @@ public void run() { LOG.info("Finished fetching metrics from servers"); LOG.info( "All: {}, Manager: {}, Garbage Collector: {}, Compactors: {}, Scan Servers: {}, Tablet Servers: {}", - allMetrics.estimatedSize(), manager.get() != null, gc.get() != null, - compactors.values().stream().mapToInt(s -> s.size()).sum(), - sservers.values().stream().mapToInt(s -> s.size()).sum(), - tservers.values().stream().mapToInt(s -> s.size()).sum()); + allMetrics.estimatedSize(), summary.getManager() != null, + summary.getGarbageCollector() != null, + summary.getCompactorAllMetricSummary().entrySet().iterator().next().getValue().count(), + summary.getSServerAllMetricSummary().entrySet().iterator().next().getValue().count(), + summary.getTServerAllMetricSummary().entrySet().iterator().next().getValue().count()); + + ResponseSummary oldSummary = summaryRef.getAndSet(summary); + oldSummary.clear(); } } + // Protect against NPE and wait for initial data gathering + private ResponseSummary getSummary() { + while (summaryRef.get() == null) { + Thread.onSpinWait(); + } + return summaryRef.get(); + } + public Set getResourceGroups() { - Set groups = new HashSet<>(); - groups.add(Constants.DEFAULT_RESOURCE_GROUP_NAME); - groups.addAll(compactors.keySet()); - groups.addAll(sservers.keySet()); - groups.addAll(tservers.keySet()); - return groups; + return getSummary().getResourceGroups(); } public Collection getProblemHosts() { - return problemHosts; + return getSummary().getProblemHosts(); } public Collection getAll() { @@ -354,7 +306,7 @@ public Collection getAll() { } public MetricResponse getManager() { - ServerId s = manager.get(); + final ServerId s = getSummary().getManager(); if (s == null) { return null; } @@ -362,32 +314,116 @@ public MetricResponse getManager() { } public MetricResponse getGarbageCollector() { - ServerId s = gc.get(); + final ServerId s = getSummary().getGarbageCollector(); if (s == null) { return null; } return allMetrics.asMap().get(s); } + public static class InstanceSummary { + private final String instanceName; + private final String instanceUUID; + private final Set zooKeepers; + private final Set volumes; + + public InstanceSummary(String instanceName, String instanceUUID, Set zooKeepers, + Set volumes) { + super(); + this.instanceName = instanceName; + this.instanceUUID = instanceUUID; + this.zooKeepers = zooKeepers; + this.volumes = volumes; + } + + public String getInstanceName() { + return instanceName; + } + + public String getInstanceUUID() { + return instanceUUID; + } + + public Set getZooKeepers() { + return zooKeepers; + } + + public Set getVolumes() { + return volumes; + } + } + + public InstanceSummary getInstanceSummary() { + return new InstanceSummary(ctx.getInstanceName(), + ctx.instanceOperations().getInstanceId().canonical(), + Set.of(ctx.getZooKeepers().split(",")), ctx.getVolumeManager().getVolumes().stream() + .map(v -> v.toString()).collect(Collectors.toSet())); + } + public Collection getCompactors(String resourceGroup) { - if (!compactors.containsKey(resourceGroup)) { + final Set servers = getSummary().getCompactorResourceGroupServers(resourceGroup); + if (servers == null) { throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); } - return allMetrics.getAllPresent(compactors.get(resourceGroup)).values(); + return allMetrics.getAllPresent(servers).values(); } - public Collection getSServers(String resourceGroup) { - if (!sservers.containsKey(resourceGroup)) { + public Map + getCompactorResourceGroupMetricSummary(String resourceGroup) { + final Map metrics = + getSummary().getCompactorResourceGroupMetricSummary(resourceGroup); + if (metrics == null) { throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); } - return allMetrics.getAllPresent(sservers.get(resourceGroup)).values(); + return metrics; } - public Collection getTServers(String resourceGroup) { - if (!tservers.containsKey(resourceGroup)) { + public Map getCompactorAllMetricSummary() { + return getSummary().getCompactorAllMetricSummary(); + } + + public Collection getScanServers(String resourceGroup) { + final Set servers = getSummary().getSServerResourceGroupServers(resourceGroup); + if (servers == null) { throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); } - return allMetrics.getAllPresent(tservers.get(resourceGroup)).values(); + return allMetrics.getAllPresent(servers).values(); + } + + public Map + getScanServerResourceGroupMetricSummary(String resourceGroup) { + final Map metrics = + getSummary().getSServerResourceGroupMetricSummary(resourceGroup); + if (metrics == null) { + throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + } + return metrics; + } + + public Map getScanServerAllMetricSummary() { + return getSummary().getSServerAllMetricSummary(); + } + + public Collection getTabletServers(String resourceGroup) { + final Set servers = getSummary().getTServerResourceGroupServers(resourceGroup); + if (servers == null) { + throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + } + return allMetrics.getAllPresent(servers).values(); + } + + public Map + getTabletServerResourceGroupMetricSummary(String resourceGroup) { + final Map metrics = + getSummary().getTServerResourceGroupMetricSummary(resourceGroup); + if (metrics == null) { + throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + } + return metrics; + } + + public Map getTabletServerAllMetricSummary() { + return getSummary().getTServerAllMetricSummary(); } public Collection getCompactions(int topN) { diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java index 8c3cb9caf11..8c4a95fa524 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java @@ -232,15 +232,28 @@ public void start() throws IOException { } }).get("/stats", ctx -> ctx.result(connStats.dump())) .get("/metrics", ctx -> ctx.json(metrics.getAll())) + .get("/metrics/instance", ctx -> ctx.json(metrics.getInstanceSummary())) .get("/metrics/groups", ctx -> ctx.json(metrics.getResourceGroups())) .get("/metrics/manager", ctx -> ctx.json(metrics.getManager())) .get("/metrics/gc", ctx -> ctx.json(metrics.getGarbageCollector())) - .get("/metrics/compactors/{group}", + .get("/metrics/compactors/summary", ctx -> ctx.json(metrics.getCompactorAllMetricSummary())) + .get("/metrics/compactors/summary/{group}", + ctx -> ctx.json(metrics.getCompactorResourceGroupMetricSummary(ctx.pathParam("group")))) + .get("/metrics/compactors/detail/{group}", ctx -> ctx.json(metrics.getCompactors(ctx.pathParam("group")))) - .get("/metrics/sservers/{group}", - ctx -> ctx.json(metrics.getSServers(ctx.pathParam("group")))) - .get("/metrics/tservers/{group}", - ctx -> ctx.json(metrics.getTServers(ctx.pathParam("group")))) + .get("/metrics/sservers/summary", ctx -> ctx.json(metrics.getScanServerAllMetricSummary())) + .get("/metrics/sservers/summary/{group}", + ctx -> ctx + .json(metrics.getScanServerResourceGroupMetricSummary(ctx.pathParam("group")))) + .get("/metrics/sservers/detail/{group}", + ctx -> ctx.json(metrics.getScanServers(ctx.pathParam("group")))) + .get("/metrics/tservers/summary", + ctx -> ctx.json(metrics.getTabletServerAllMetricSummary())) + .get("/metrics/tservers/summary/{group}", + ctx -> ctx + .json(metrics.getTabletServerResourceGroupMetricSummary(ctx.pathParam("group")))) + .get("/metrics/tservers/detail/{group}", + ctx -> ctx.json(metrics.getTabletServers(ctx.pathParam("group")))) .get("/metrics/problems", ctx -> ctx.json(metrics.getProblemHosts())) .get("/metrics/compactions", ctx -> ctx.json(metrics.getCompactions(25))) .get("/metrics/compactions/{num}", diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/ResponseSummary.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/ResponseSummary.java new file mode 100644 index 00000000000..e1ead488fc8 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/ResponseSummary.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.metrics.flatbuffers.FMetric; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.benmanes.caffeine.cache.Cache; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Meter.Type; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; + +public class ResponseSummary { + + private static final Logger LOG = LoggerFactory.getLogger(ResponseSummary.class); + + private final Cache allMetrics; + + private final Set resourceGroups = new HashSet<>(); + private final Set problemHosts = new HashSet<>(); + private final AtomicReference manager = new AtomicReference<>(); + private final AtomicReference gc = new AtomicReference<>(); + + // index of resource group name to set of servers + private final Map> compactors = new ConcurrentHashMap<>(); + private final Map> sservers = new ConcurrentHashMap<>(); + private final Map> tservers = new ConcurrentHashMap<>(); + + // Summaries of metrics by server type + // map of metric name to metric values + private final Map totalCompactorMetrics = new ConcurrentHashMap<>(); + private final Map totalSServerMetrics = new ConcurrentHashMap<>(); + private final Map totalTServerMetrics = new ConcurrentHashMap<>(); + + // Summaries of metrics by server type and resource group + // map of resource group to metric name to metric values + private final Map> rgCompactorMetrics = + new ConcurrentHashMap<>(); + private final Map> rgSServerMetrics = + new ConcurrentHashMap<>(); + private final Map> rgTServerMetrics = + new ConcurrentHashMap<>(); + + public ResponseSummary(Cache allMetrics) { + this.allMetrics = allMetrics; + } + + public void clear() { + resourceGroups.clear(); + compactors.clear(); + sservers.clear(); + tservers.clear(); + totalCompactorMetrics.clear(); + totalSServerMetrics.clear(); + totalTServerMetrics.clear(); + rgCompactorMetrics.clear(); + rgSServerMetrics.clear(); + rgTServerMetrics.clear(); + } + + private void updateAggregates(final MetricResponse response, + final Map total, + final Map> rg) { + + final Map rgMetrics = rg.computeIfAbsent( + response.getResourceGroup(), (k) -> new ConcurrentHashMap()); + + response.getMetrics().forEach((bb) -> { + final FMetric fm = FMetric.getRootAsFMetric(bb); + final String name = fm.name(); + double value = fm.dvalue(); + if (value == 0.0) { + value = fm.ivalue(); + if (value == 0.0) { + value = fm.lvalue(); + } + } + final Meter.Id id = new Meter.Id(name, Tags.empty(), null, null, Type.valueOf(fm.type())); + total.computeIfAbsent(name, (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, + DistributionStatisticConfig.NONE, 1.0, false)).record(value); + rgMetrics.computeIfAbsent(name, (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, + DistributionStatisticConfig.NONE, 1.0, false)).record(value); + }); + + } + + public void processResponse(final ServerId server, final MetricResponse response) { + problemHosts.remove(server); + allMetrics.put(server, response); + resourceGroups.add(response.getResourceGroup()); + switch (response.serverType) { + case COMPACTOR: + compactors.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()) + .add(server); + updateAggregates(response, totalCompactorMetrics, rgCompactorMetrics); + break; + case GARBAGE_COLLECTOR: + if (gc.get() == null || !gc.get().equals(server)) { + gc.set(server); + } + break; + case MANAGER: + if (manager.get() == null || !manager.get().equals(server)) { + manager.set(server); + } + break; + case SCAN_SERVER: + sservers.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()).add(server); + updateAggregates(response, totalSServerMetrics, rgSServerMetrics); + break; + case TABLET_SERVER: + tservers.computeIfAbsent(response.getResourceGroup(), (rg) -> new HashSet<>()).add(server); + updateAggregates(response, totalTServerMetrics, rgTServerMetrics); + break; + default: + LOG.error("Unhandled server type in fetch metric response: {}", response.serverType); + break; + } + + } + + public void processError(ServerId server) { + problemHosts.add(server); + } + + public Set getResourceGroups() { + return this.resourceGroups; + } + + public Set getProblemHosts() { + return this.problemHosts; + } + + public ServerId getManager() { + return this.manager.get(); + } + + public ServerId getGarbageCollector() { + return this.gc.get(); + } + + public Set getCompactorResourceGroupServers(String resourceGroup) { + return this.compactors.get(resourceGroup); + } + + public Map + getCompactorResourceGroupMetricSummary(String resourceGroup) { + return this.rgCompactorMetrics.get(resourceGroup); + } + + public Map getCompactorAllMetricSummary() { + return this.totalCompactorMetrics; + } + + public Set getSServerResourceGroupServers(String resourceGroup) { + return this.sservers.get(resourceGroup); + } + + public Map + getSServerResourceGroupMetricSummary(String resourceGroup) { + return this.rgSServerMetrics.get(resourceGroup); + } + + public Map getSServerAllMetricSummary() { + return this.totalSServerMetrics; + } + + public Set getTServerResourceGroupServers(String resourceGroup) { + return this.tservers.get(resourceGroup); + } + + public Map + getTServerResourceGroupMetricSummary(String resourceGroup) { + return this.rgTServerMetrics.get(resourceGroup); + } + + public Map getTServerAllMetricSummary() { + return this.totalTServerMetrics; + } + +} From 25ff6c53deb752aef594df257aa2e2ee2bca0e1e Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Wed, 30 Oct 2024 14:01:16 +0000 Subject: [PATCH 13/22] Changes from refactoring and testing --- .../org/apache/accumulo/monitor/Monitor.java | 1 + .../InformationFetcher.java} | 47 +++++------ .../monitor/{ => next}/NewMonitor.java | 81 +++---------------- .../SystemInformation.java} | 63 +++++++++------ ...mulativeDistributionSummarySerializer.java | 43 ++++++++++ .../serializers/MetricResponseSerializer.java | 72 +++++++++++++++++ .../next/serializers/ThriftSerializer.java | 50 ++++++++++++ .../test/functional/NewMonitorSslIT.java | 2 +- 8 files changed, 238 insertions(+), 121 deletions(-) rename server/monitor/src/main/java/org/apache/accumulo/monitor/{MetricsFetcher.java => next/InformationFetcher.java} (91%) rename server/monitor/src/main/java/org/apache/accumulo/monitor/{ => next}/NewMonitor.java (77%) rename server/monitor/src/main/java/org/apache/accumulo/monitor/{ResponseSummary.java => next/SystemInformation.java} (73%) create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/CumulativeDistributionSummarySerializer.java create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/ThriftSerializer.java diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java index 9798f7dceaa..cca906f978c 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java @@ -80,6 +80,7 @@ import org.apache.accumulo.core.util.Halt; import org.apache.accumulo.core.util.Pair; import org.apache.accumulo.core.util.threads.Threads; +import org.apache.accumulo.monitor.next.NewMonitor; import org.apache.accumulo.monitor.rest.compactions.external.ExternalCompactionInfo; import org.apache.accumulo.monitor.rest.compactions.external.RunningCompactions; import org.apache.accumulo.monitor.rest.compactions.external.RunningCompactorDetails; diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java similarity index 91% rename from server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java rename to server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index a02a5dd7cb8..48f7ad016a4 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/MetricsFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.accumulo.monitor; +package org.apache.accumulo.monitor.next; import static java.util.concurrent.TimeUnit.SECONDS; @@ -54,7 +54,6 @@ import org.apache.accumulo.core.util.threads.ThreadPools; import org.apache.accumulo.core.util.threads.ThreadPools.ExecutionError; import org.apache.accumulo.server.ServerContext; -import org.apache.thrift.TException; import org.apache.thrift.transport.TTransportException; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.jetty.util.NanoTime; @@ -68,11 +67,11 @@ import com.github.benmanes.caffeine.cache.Scheduler; import com.google.common.net.HostAndPort; -import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; -public class MetricsFetcher implements RemovalListener, Runnable { +public class InformationFetcher implements RemovalListener, Runnable { - private static final Logger LOG = LoggerFactory.getLogger(MetricsFetcher.class); + private static final Logger LOG = LoggerFactory.getLogger(InformationFetcher.class); private static class GcServerId extends ServerId { private GcServerId(String resourceGroup, String host, int port) { @@ -85,9 +84,9 @@ private class MetricFetcher implements Runnable { private final ServerContext ctx; private final ServerId server; - private final ResponseSummary summary; + private final SystemInformation summary; - private MetricFetcher(ServerContext ctx, ServerId server, ResponseSummary summary) { + private MetricFetcher(ServerContext ctx, ServerId server, SystemInformation summary) { this.ctx = ctx; this.server = server; this.summary = summary; @@ -104,7 +103,7 @@ public void run() { } finally { ThriftUtil.returnClient(metricsClient, ctx); } - } catch (TException e) { + } catch (Exception e) { LOG.warn("Error trying to get metrics from server: {}", server); summary.processError(server); } @@ -177,14 +176,14 @@ public void run() { private final AtomicBoolean newConnectionEvent = new AtomicBoolean(false); private final Cache allMetrics; - private final AtomicReference summaryRef = new AtomicReference<>(); + private final AtomicReference summaryRef = new AtomicReference<>(); private final AtomicReference> runningCompactions = new AtomicReference<>(); private final AtomicReference> runningCompactionsDurationIndex = new AtomicReference<>(); - public MetricsFetcher(ServerContext ctx, Supplier connectionCount) { + public InformationFetcher(ServerContext ctx, Supplier connectionCount) { this.ctx = ctx; this.connectionCount = connectionCount; this.allMetrics = Caffeine.newBuilder().executor(pool).scheduler(Scheduler.systemScheduler()) @@ -226,7 +225,7 @@ public void run() { LOG.info("Fetching metrics from servers"); final List> futures = new ArrayList<>(); - final ResponseSummary summary = new ResponseSummary(allMetrics); + final SystemInformation summary = new SystemInformation(allMetrics); for (ServerId.Type type : ServerId.Type.values()) { for (ServerId server : this.ctx.instanceOperations().getServers(type)) { @@ -279,14 +278,16 @@ public void run() { summary.getSServerAllMetricSummary().entrySet().iterator().next().getValue().count(), summary.getTServerAllMetricSummary().entrySet().iterator().next().getValue().count()); - ResponseSummary oldSummary = summaryRef.getAndSet(summary); - oldSummary.clear(); + SystemInformation oldSummary = summaryRef.getAndSet(summary); + if (oldSummary != null) { + oldSummary.clear(); + } } } // Protect against NPE and wait for initial data gathering - private ResponseSummary getSummary() { + private SystemInformation getSummary() { while (summaryRef.get() == null) { Thread.onSpinWait(); } @@ -368,9 +369,9 @@ public Collection getCompactors(String resourceGroup) { return allMetrics.getAllPresent(servers).values(); } - public Map + public Map getCompactorResourceGroupMetricSummary(String resourceGroup) { - final Map metrics = + final Map metrics = getSummary().getCompactorResourceGroupMetricSummary(resourceGroup); if (metrics == null) { throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); @@ -378,7 +379,7 @@ public Collection getCompactors(String resourceGroup) { return metrics; } - public Map getCompactorAllMetricSummary() { + public Map getCompactorAllMetricSummary() { return getSummary().getCompactorAllMetricSummary(); } @@ -390,9 +391,9 @@ public Collection getScanServers(String resourceGroup) { return allMetrics.getAllPresent(servers).values(); } - public Map + public Map getScanServerResourceGroupMetricSummary(String resourceGroup) { - final Map metrics = + final Map metrics = getSummary().getSServerResourceGroupMetricSummary(resourceGroup); if (metrics == null) { throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); @@ -400,7 +401,7 @@ public Collection getScanServers(String resourceGroup) { return metrics; } - public Map getScanServerAllMetricSummary() { + public Map getScanServerAllMetricSummary() { return getSummary().getSServerAllMetricSummary(); } @@ -412,9 +413,9 @@ public Collection getTabletServers(String resourceGroup) { return allMetrics.getAllPresent(servers).values(); } - public Map + public Map getTabletServerResourceGroupMetricSummary(String resourceGroup) { - final Map metrics = + final Map metrics = getSummary().getTServerResourceGroupMetricSummary(resourceGroup); if (metrics == null) { throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); @@ -422,7 +423,7 @@ public Collection getTabletServers(String resourceGroup) { return metrics; } - public Map getTabletServerAllMetricSummary() { + public Map getTabletServerAllMetricSummary() { return getSummary().getTServerAllMetricSummary(); } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java similarity index 77% rename from server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java rename to server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java index 8c4a95fa524..8cda5d263f1 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java @@ -16,27 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.accumulo.monitor; +package org.apache.accumulo.monitor.next; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; -import java.nio.ByteBuffer; import java.util.EnumSet; import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.Property; -import org.apache.accumulo.core.metrics.flatbuffers.FMetric; -import org.apache.accumulo.core.metrics.flatbuffers.FTag; import org.apache.accumulo.core.metrics.thrift.MetricResponse; import org.apache.accumulo.core.tabletserver.thrift.TExternalCompactionJob; import org.apache.accumulo.core.util.threads.Threads; +import org.apache.accumulo.monitor.next.serializers.CumulativeDistributionSummarySerializer; +import org.apache.accumulo.monitor.next.serializers.MetricResponseSerializer; +import org.apache.accumulo.monitor.next.serializers.ThriftSerializer; import org.apache.accumulo.server.ServerContext; -import org.apache.thrift.TBase; -import org.apache.thrift.TException; -import org.apache.thrift.TSerializer; -import org.apache.thrift.protocol.TSimpleJSONProtocol; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.ConnectionStatistics; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -46,77 +42,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.javalin.Javalin; import io.javalin.json.JavalinJackson; import io.javalin.security.RouteRole; +import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; public class NewMonitor implements Connection.Listener { - public static class ThriftSerializer extends JsonSerializer> { - - private final TSimpleJSONProtocol.Factory factory = new TSimpleJSONProtocol.Factory(); - - @Override - public void serialize(TBase value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - try { - // TSerializer is likely not thread safe - gen.writeRaw(new TSerializer(factory).toString(value)); - } catch (TException e) { - LOG.error("Error serializing Thrift object", e); - } - } - - } - - public static class MetricResponseSerializer extends JsonSerializer { - - @Override - public void serialize(MetricResponse value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - gen.writeStartObject(); - gen.writeNumberField("timestamp", value.getTimestamp()); - gen.writeStringField("serverType", value.getServerType().toString()); - gen.writeStringField("resourceGroup", value.getResourceGroup()); - gen.writeStringField("host", value.getServer()); - gen.writeArrayFieldStart("metrics"); - for (final ByteBuffer binary : value.getMetrics()) { - FMetric fm = FMetric.getRootAsFMetric(binary); - gen.writeStartObject(); - gen.writeStringField("name", fm.name()); - gen.writeStringField("type", fm.type()); - gen.writeArrayFieldStart("tags"); - for (int i = 0; i < fm.tagsLength(); i++) { - FTag t = fm.tags(i); - gen.writeStartObject(); - gen.writeStringField(t.key(), t.value()); - gen.writeEndObject(); - } - gen.writeEndArray(); - // Write the non-zero number as the value - if (fm.lvalue() > 0) { - gen.writeNumberField("value", fm.lvalue()); - } else if (fm.ivalue() > 0) { - gen.writeNumberField("value", fm.ivalue()); - } else if (fm.dvalue() > 0.0d) { - gen.writeNumberField("value", fm.dvalue()); - } else { - gen.writeNumberField("value", 0); - } - gen.writeEndObject(); - } - gen.writeEndArray(); - gen.writeEndObject(); - } - - } - private static final Logger LOG = LoggerFactory.getLogger(NewMonitor.class); private static final EnumSet requireForSecure = @@ -129,7 +64,7 @@ public void serialize(MetricResponse value, JsonGenerator gen, SerializerProvide private final String hostname; private final boolean secure; private final ConnectionStatistics connStats; - private final MetricsFetcher metrics; + private final InformationFetcher metrics; public NewMonitor(ServerContext ctx, String hostname) { this.ctx = ctx; @@ -138,7 +73,7 @@ public NewMonitor(ServerContext ctx, String hostname) { .allMatch(s -> s != null && !s.isEmpty()); this.connStats = new ConnectionStatistics(); - this.metrics = new MetricsFetcher(ctx, connStats::getConnections); + this.metrics = new InformationFetcher(ctx, connStats::getConnections); } @SuppressFBWarnings(value = "UNENCRYPTED_SERVER_SOCKET", @@ -165,6 +100,8 @@ public void start() throws IOException { module.addSerializer(MetricResponse.class, new MetricResponseSerializer()); module.addSerializer(TExternalCompaction.class, new ThriftSerializer()); module.addSerializer(TExternalCompactionJob.class, new ThriftSerializer()); + module.addSerializer(CumulativeDistributionSummary.class, + new CumulativeDistributionSummarySerializer()); mapper.registerModule(module); })); diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/ResponseSummary.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java similarity index 73% rename from server/monitor/src/main/java/org/apache/accumulo/monitor/ResponseSummary.java rename to server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java index e1ead488fc8..7ce5ba05cfb 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/ResponseSummary.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.accumulo.monitor; +package org.apache.accumulo.monitor.next; +import java.time.Duration; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -33,16 +34,20 @@ import com.github.benmanes.caffeine.cache.Cache; import io.micrometer.core.instrument.Clock; -import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Meter.Type; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; -public class ResponseSummary { +public class SystemInformation { - private static final Logger LOG = LoggerFactory.getLogger(ResponseSummary.class); + private static final Logger LOG = LoggerFactory.getLogger(SystemInformation.class); + + private final DistributionStatisticConfig DSC = + DistributionStatisticConfig.builder().percentilePrecision(1).minimumExpectedValue(0.1) + .maximumExpectedValue(Double.POSITIVE_INFINITY).expiry(Duration.ofMinutes(10)) + .bufferLength(3).build(); private final Cache allMetrics; @@ -58,20 +63,23 @@ public class ResponseSummary { // Summaries of metrics by server type // map of metric name to metric values - private final Map totalCompactorMetrics = new ConcurrentHashMap<>(); - private final Map totalSServerMetrics = new ConcurrentHashMap<>(); - private final Map totalTServerMetrics = new ConcurrentHashMap<>(); + private final Map totalCompactorMetrics = + new ConcurrentHashMap<>(); + private final Map totalSServerMetrics = + new ConcurrentHashMap<>(); + private final Map totalTServerMetrics = + new ConcurrentHashMap<>(); // Summaries of metrics by server type and resource group // map of resource group to metric name to metric values - private final Map> rgCompactorMetrics = + private final Map> rgCompactorMetrics = new ConcurrentHashMap<>(); - private final Map> rgSServerMetrics = + private final Map> rgSServerMetrics = new ConcurrentHashMap<>(); - private final Map> rgTServerMetrics = + private final Map> rgTServerMetrics = new ConcurrentHashMap<>(); - public ResponseSummary(Cache allMetrics) { + public SystemInformation(Cache allMetrics) { this.allMetrics = allMetrics; } @@ -89,11 +97,12 @@ public void clear() { } private void updateAggregates(final MetricResponse response, - final Map total, - final Map> rg) { + final Map total, + final Map> rg) { - final Map rgMetrics = rg.computeIfAbsent( - response.getResourceGroup(), (k) -> new ConcurrentHashMap()); + final Map rgMetrics = + rg.computeIfAbsent(response.getResourceGroup(), + (k) -> new ConcurrentHashMap()); response.getMetrics().forEach((bb) -> { final FMetric fm = FMetric.getRootAsFMetric(bb); @@ -106,10 +115,14 @@ private void updateAggregates(final MetricResponse response, } } final Meter.Id id = new Meter.Id(name, Tags.empty(), null, null, Type.valueOf(fm.type())); - total.computeIfAbsent(name, (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, - DistributionStatisticConfig.NONE, 1.0, false)).record(value); - rgMetrics.computeIfAbsent(name, (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, - DistributionStatisticConfig.NONE, 1.0, false)).record(value); + total + .computeIfAbsent(name, + (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, DSC, 1.0, false)) + .record(value); + rgMetrics + .computeIfAbsent(name, + (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, DSC, 1.0, false)) + .record(value); }); } @@ -173,12 +186,12 @@ public Set getCompactorResourceGroupServers(String resourceGroup) { return this.compactors.get(resourceGroup); } - public Map + public Map getCompactorResourceGroupMetricSummary(String resourceGroup) { return this.rgCompactorMetrics.get(resourceGroup); } - public Map getCompactorAllMetricSummary() { + public Map getCompactorAllMetricSummary() { return this.totalCompactorMetrics; } @@ -186,12 +199,12 @@ public Set getSServerResourceGroupServers(String resourceGroup) { return this.sservers.get(resourceGroup); } - public Map + public Map getSServerResourceGroupMetricSummary(String resourceGroup) { return this.rgSServerMetrics.get(resourceGroup); } - public Map getSServerAllMetricSummary() { + public Map getSServerAllMetricSummary() { return this.totalSServerMetrics; } @@ -199,12 +212,12 @@ public Set getTServerResourceGroupServers(String resourceGroup) { return this.tservers.get(resourceGroup); } - public Map + public Map getTServerResourceGroupMetricSummary(String resourceGroup) { return this.rgTServerMetrics.get(resourceGroup); } - public Map getTServerAllMetricSummary() { + public Map getTServerAllMetricSummary() { return this.totalTServerMetrics; } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/CumulativeDistributionSummarySerializer.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/CumulativeDistributionSummarySerializer.java new file mode 100644 index 00000000000..cc655c33548 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/CumulativeDistributionSummarySerializer.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor.next.serializers; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; + +public class CumulativeDistributionSummarySerializer + extends JsonSerializer { + + @Override + public void serialize(CumulativeDistributionSummary value, JsonGenerator gen, + SerializerProvider serializers) throws IOException { + + gen.writeStartObject(); + gen.writeStringField("count", Long.toString(value.count())); + gen.writeStringField("mean", Double.toString(value.mean())); + gen.writeStringField("max", Double.toString(value.max())); + gen.writeEndObject(); + } + +} diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java new file mode 100644 index 00000000000..f1db0665f21 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor.next.serializers; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.accumulo.core.metrics.flatbuffers.FMetric; +import org.apache.accumulo.core.metrics.flatbuffers.FTag; +import org.apache.accumulo.core.metrics.thrift.MetricResponse; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class MetricResponseSerializer extends JsonSerializer { + + @Override + public void serialize(MetricResponse value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeStartObject(); + gen.writeNumberField("timestamp", value.getTimestamp()); + gen.writeStringField("serverType", value.getServerType().toString()); + gen.writeStringField("resourceGroup", value.getResourceGroup()); + gen.writeStringField("host", value.getServer()); + gen.writeArrayFieldStart("metrics"); + for (final ByteBuffer binary : value.getMetrics()) { + FMetric fm = FMetric.getRootAsFMetric(binary); + gen.writeStartObject(); + gen.writeStringField("name", fm.name()); + gen.writeStringField("type", fm.type()); + gen.writeArrayFieldStart("tags"); + for (int i = 0; i < fm.tagsLength(); i++) { + FTag t = fm.tags(i); + gen.writeStartObject(); + gen.writeStringField(t.key(), t.value()); + gen.writeEndObject(); + } + gen.writeEndArray(); + // Write the non-zero number as the value + if (fm.lvalue() > 0) { + gen.writeNumberField("value", fm.lvalue()); + } else if (fm.ivalue() > 0) { + gen.writeNumberField("value", fm.ivalue()); + } else if (fm.dvalue() > 0.0d) { + gen.writeNumberField("value", fm.dvalue()); + } else { + gen.writeNumberField("value", 0); + } + gen.writeEndObject(); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + +} diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/ThriftSerializer.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/ThriftSerializer.java new file mode 100644 index 00000000000..222ef8c2f85 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/ThriftSerializer.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor.next.serializers; + +import java.io.IOException; + +import org.apache.thrift.TBase; +import org.apache.thrift.TException; +import org.apache.thrift.TSerializer; +import org.apache.thrift.protocol.TSimpleJSONProtocol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class ThriftSerializer extends JsonSerializer> { + + private static final Logger LOG = LoggerFactory.getLogger(ThriftSerializer.class); + private final TSimpleJSONProtocol.Factory factory = new TSimpleJSONProtocol.Factory(); + + @Override + public void serialize(TBase value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + try { + // TSerializer is likely not thread safe + gen.writeRaw(new TSerializer(factory).toString(value)); + } catch (TException e) { + LOG.error("Error serializing Thrift object", e); + } + } + +} diff --git a/test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java b/test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java index 75e175a76ab..3dce0418c91 100644 --- a/test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java +++ b/test/src/main/java/org/apache/accumulo/test/functional/NewMonitorSslIT.java @@ -44,7 +44,7 @@ import org.apache.accumulo.core.util.MonitorUtil; import org.apache.accumulo.minicluster.ServerType; import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl; -import org.apache.accumulo.monitor.NewMonitor; +import org.apache.accumulo.monitor.next.NewMonitor; import org.apache.hadoop.conf.Configuration; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; From 8918cf4250e0a385a166dabd3ad4fd6c0973ea81 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Tue, 5 Nov 2024 20:26:46 +0000 Subject: [PATCH 14/22] Return 404 on unknown resource group, empty object when known but no servers --- .../monitor/next/InformationFetcher.java | 95 +++++++++++-------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index 48f7ad016a4..47410c40bfd 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -38,6 +38,8 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import jakarta.ws.rs.NotFoundException; + import org.apache.accumulo.core.client.admin.servers.ServerId; import org.apache.accumulo.core.compaction.thrift.CompactionCoordinatorService; import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; @@ -73,6 +75,38 @@ public class InformationFetcher implements RemovalListener zooKeepers; + private final Set volumes; + + public InstanceSummary(String instanceName, String instanceUUID, Set zooKeepers, + Set volumes) { + super(); + this.instanceName = instanceName; + this.instanceUUID = instanceUUID; + this.zooKeepers = zooKeepers; + this.volumes = volumes; + } + + public String getInstanceName() { + return instanceName; + } + + public String getInstanceUUID() { + return instanceUUID; + } + + public Set getZooKeepers() { + return zooKeepers; + } + + public Set getVolumes() { + return volumes; + } + } + private static class GcServerId extends ServerId { private GcServerId(String resourceGroup, String host, int port) { // TODO: This is a little wonky, Type.GC does not exist in the public API, @@ -294,6 +328,13 @@ private SystemInformation getSummary() { return summaryRef.get(); } + private void validateResourceGroup(String resourceGroup) { + if (getSummary().getResourceGroups().contains(resourceGroup)) { + return; + } + throw new NotFoundException("Resource Group " + resourceGroup + " not found"); + } + public Set getResourceGroups() { return getSummary().getResourceGroups(); } @@ -309,7 +350,7 @@ public Collection getAll() { public MetricResponse getManager() { final ServerId s = getSummary().getManager(); if (s == null) { - return null; + throw new NotFoundException("Manager not found"); } return allMetrics.asMap().get(s); } @@ -317,43 +358,11 @@ public MetricResponse getManager() { public MetricResponse getGarbageCollector() { final ServerId s = getSummary().getGarbageCollector(); if (s == null) { - return null; + throw new NotFoundException("Garbage Collector not found"); } return allMetrics.asMap().get(s); } - public static class InstanceSummary { - private final String instanceName; - private final String instanceUUID; - private final Set zooKeepers; - private final Set volumes; - - public InstanceSummary(String instanceName, String instanceUUID, Set zooKeepers, - Set volumes) { - super(); - this.instanceName = instanceName; - this.instanceUUID = instanceUUID; - this.zooKeepers = zooKeepers; - this.volumes = volumes; - } - - public String getInstanceName() { - return instanceName; - } - - public String getInstanceUUID() { - return instanceUUID; - } - - public Set getZooKeepers() { - return zooKeepers; - } - - public Set getVolumes() { - return volumes; - } - } - public InstanceSummary getInstanceSummary() { return new InstanceSummary(ctx.getInstanceName(), ctx.instanceOperations().getInstanceId().canonical(), @@ -362,19 +371,21 @@ public InstanceSummary getInstanceSummary() { } public Collection getCompactors(String resourceGroup) { + validateResourceGroup(resourceGroup); final Set servers = getSummary().getCompactorResourceGroupServers(resourceGroup); if (servers == null) { - throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + return List.of(); } return allMetrics.getAllPresent(servers).values(); } public Map getCompactorResourceGroupMetricSummary(String resourceGroup) { + validateResourceGroup(resourceGroup); final Map metrics = getSummary().getCompactorResourceGroupMetricSummary(resourceGroup); if (metrics == null) { - throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + return Map.of(); } return metrics; } @@ -384,19 +395,21 @@ public Map getCompactorAllMetricSummary() } public Collection getScanServers(String resourceGroup) { + validateResourceGroup(resourceGroup); final Set servers = getSummary().getSServerResourceGroupServers(resourceGroup); if (servers == null) { - throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + return List.of(); } return allMetrics.getAllPresent(servers).values(); } public Map getScanServerResourceGroupMetricSummary(String resourceGroup) { + validateResourceGroup(resourceGroup); final Map metrics = getSummary().getSServerResourceGroupMetricSummary(resourceGroup); if (metrics == null) { - throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + return Map.of(); } return metrics; } @@ -406,19 +419,21 @@ public Map getScanServerAllMetricSummary() } public Collection getTabletServers(String resourceGroup) { + validateResourceGroup(resourceGroup); final Set servers = getSummary().getTServerResourceGroupServers(resourceGroup); if (servers == null) { - throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + return List.of(); } return allMetrics.getAllPresent(servers).values(); } public Map getTabletServerResourceGroupMetricSummary(String resourceGroup) { + validateResourceGroup(resourceGroup); final Map metrics = getSummary().getTServerResourceGroupMetricSummary(resourceGroup); if (metrics == null) { - throw new IllegalArgumentException("Resource Group " + resourceGroup + " not found."); + return Map.of(); } return metrics; } From de0afb0d8c6c3b5397e17ebd77e5faaf9df139c4 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Wed, 6 Nov 2024 16:43:33 +0000 Subject: [PATCH 15/22] Map exception to http error code --- .../java/org/apache/accumulo/monitor/next/NewMonitor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java index 8cda5d263f1..c54e38708d7 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java @@ -23,6 +23,8 @@ import java.net.ServerSocket; import java.util.EnumSet; +import jakarta.ws.rs.NotFoundException; + import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.Property; @@ -195,7 +197,7 @@ public void start() throws IOException { .get("/metrics/compactions", ctx -> ctx.json(metrics.getCompactions(25))) .get("/metrics/compactions/{num}", ctx -> ctx.json(metrics.getCompactions(Integer.parseInt(ctx.pathParam("num"))))) - .start(); + .exception(NotFoundException.class, (e, ctx) -> ctx.status(404)).start(); LOG.info("New Monitor listening on port: {}", httpPort); } From 4cef91dc3af532dca3d5aecbdbdfb60935b281d9 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Thu, 7 Nov 2024 15:10:28 +0000 Subject: [PATCH 16/22] Added /metrics/tables and /metrics/tables/{name} endpoints --- assemble/pom.xml | 5 + server/monitor/pom.xml | 4 + .../monitor/next/InformationFetcher.java | 43 ++++ .../accumulo/monitor/next/NewMonitor.java | 7 + .../monitor/next/SystemInformation.java | 209 ++++++++++++++++++ .../next/serializers/TabletIdSerializer.java | 37 ++++ 6 files changed, 305 insertions(+) create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/TabletIdSerializer.java diff --git a/assemble/pom.xml b/assemble/pom.xml index 8b1773d32f8..a2aa8b722de 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -56,6 +56,11 @@ jackson-databind true + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + true + com.fasterxml.jackson.jakarta.rs jackson-jakarta-rs-base diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index a8453fa665b..b0ae039cc12 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -39,6 +39,10 @@ com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + com.github.ben-manes.caffeine caffeine diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index 47410c40bfd..6d53d8c6801 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -37,13 +37,17 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import jakarta.ws.rs.NotFoundException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.admin.TabletInformation; import org.apache.accumulo.core.client.admin.servers.ServerId; import org.apache.accumulo.core.compaction.thrift.CompactionCoordinatorService; import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; import org.apache.accumulo.core.compaction.thrift.TExternalCompactionList; +import org.apache.accumulo.core.data.Range; import org.apache.accumulo.core.lock.ServiceLockData; import org.apache.accumulo.core.lock.ServiceLockData.ThriftService; import org.apache.accumulo.core.lock.ServiceLockPaths.ServiceLockPath; @@ -55,6 +59,7 @@ import org.apache.accumulo.core.util.UtilWaitThread; import org.apache.accumulo.core.util.threads.ThreadPools; import org.apache.accumulo.core.util.threads.ThreadPools.ExecutionError; +import org.apache.accumulo.monitor.next.SystemInformation.TableSummary; import org.apache.accumulo.server.ServerContext; import org.apache.thrift.transport.TTransportException; import org.checkerframework.checker.nullness.qual.Nullable; @@ -145,6 +150,31 @@ public void run() { } + private class TableInformationFetcher implements Runnable { + private final ServerContext ctx; + private final String table; + private final SystemInformation summary; + + private TableInformationFetcher(ServerContext ctx, String tableName, + SystemInformation summary) { + this.ctx = ctx; + this.table = tableName; + this.summary = summary; + } + + @Override + public void run() { + try (Stream tablets = + this.ctx.tableOperations().getTabletInformation(table, new Range())) { + tablets.forEach(t -> summary.processTabletInformation(table, t)); + } catch (TableNotFoundException e) { + LOG.warn( + "TableNotFoundException thrown while trying to gather information for table: " + table, + e); + } + } + } + private class CompactionListFetcher implements Runnable { private final String coordinatorMissingMsg = @@ -284,6 +314,11 @@ public void run() { // Fetch external compaction information from the Manager futures.add(this.pool.submit(new CompactionListFetcher())); + // Fetch Tablet / Tablet information from the metadata table + for (String tName : this.ctx.tableOperations().list()) { + futures.add(this.pool.submit(new TableInformationFetcher(this.ctx, tName, summary))); + } + while (futures.size() > 0) { Iterator> iter = futures.iterator(); while (iter.hasNext()) { @@ -451,4 +486,12 @@ public Collection getCompactions(int topN) { } return results; } + + public Map getTables() { + return getSummary().getTables(); + } + + public List getTablets(String tableName) { + return getSummary().getTablets(tableName); + } } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java index c54e38708d7..ec23be012d1 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java @@ -28,11 +28,13 @@ import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.data.TabletId; import org.apache.accumulo.core.metrics.thrift.MetricResponse; import org.apache.accumulo.core.tabletserver.thrift.TExternalCompactionJob; import org.apache.accumulo.core.util.threads.Threads; import org.apache.accumulo.monitor.next.serializers.CumulativeDistributionSummarySerializer; import org.apache.accumulo.monitor.next.serializers.MetricResponseSerializer; +import org.apache.accumulo.monitor.next.serializers.TabletIdSerializer; import org.apache.accumulo.monitor.next.serializers.ThriftSerializer; import org.apache.accumulo.server.ServerContext; import org.eclipse.jetty.io.Connection; @@ -45,6 +47,7 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.javalin.Javalin; @@ -104,7 +107,9 @@ public void start() throws IOException { module.addSerializer(TExternalCompactionJob.class, new ThriftSerializer()); module.addSerializer(CumulativeDistributionSummary.class, new CumulativeDistributionSummarySerializer()); + module.addSerializer(TabletId.class, new TabletIdSerializer()); mapper.registerModule(module); + mapper.registerModule(new Jdk8Module()); })); final HttpConnectionFactory httpFactory = new HttpConnectionFactory(); @@ -197,6 +202,8 @@ public void start() throws IOException { .get("/metrics/compactions", ctx -> ctx.json(metrics.getCompactions(25))) .get("/metrics/compactions/{num}", ctx -> ctx.json(metrics.getCompactions(Integer.parseInt(ctx.pathParam("num"))))) + .get("/metrics/tables", ctx -> ctx.json(metrics.getTables())) + .get("/metrics/tables/{name}", ctx -> ctx.json(metrics.getTablets(ctx.pathParam("name")))) .exception(NotFoundException.class, (e, ctx) -> ctx.status(404)).start(); LOG.info("New Monitor listening on port: {}", httpPort); diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java index 7ce5ba05cfb..97dae79c54b 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java @@ -19,13 +19,23 @@ package org.apache.accumulo.monitor.next; import java.time.Duration; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import org.apache.accumulo.core.client.admin.TabletAvailability; +import org.apache.accumulo.core.client.admin.TabletInformation; import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.data.TabletId; +import org.apache.accumulo.core.dataImpl.KeyExtent; +import org.apache.accumulo.core.dataImpl.TabletIdImpl; +import org.apache.accumulo.core.metadata.TabletState; import org.apache.accumulo.core.metrics.flatbuffers.FMetric; import org.apache.accumulo.core.metrics.thrift.MetricResponse; import org.slf4j.Logger; @@ -42,6 +52,187 @@ public class SystemInformation { + public static class ObfuscatedTabletId extends TabletIdImpl { + + public ObfuscatedTabletId(KeyExtent ke) { + super(ke); + } + + @Override + public String toString() { + return this.toKeyExtent().obscured(); + } + + } + + public static class SanitizedTabletInformation implements TabletInformation { + private final TabletInformation tabletInfo; + + public SanitizedTabletInformation(TabletInformation tabletInfo) { + super(); + this.tabletInfo = tabletInfo; + } + + @Override + public TabletId getTabletId() { + return new ObfuscatedTabletId(((TabletIdImpl) tabletInfo.getTabletId()).toKeyExtent()); + } + + @Override + public int getNumFiles() { + return tabletInfo.getNumFiles(); + } + + @Override + public int getNumWalLogs() { + return tabletInfo.getNumWalLogs(); + } + + @Override + public long getEstimatedEntries() { + return tabletInfo.getEstimatedEntries(); + } + + @Override + public long getEstimatedSize() { + return tabletInfo.getEstimatedSize(); + } + + @Override + public String getTabletState() { + return tabletInfo.getTabletState(); + } + + @Override + public Optional getLocation() { + // TODO Auto-generated method stub + return Optional.empty(); + } + + @Override + public String getTabletDir() { + return tabletInfo.getTabletDir(); + } + + @Override + public TabletAvailability getTabletAvailability() { + return tabletInfo.getTabletAvailability(); + } + + } + + public static class TableSummary { + + private final AtomicLong totalEntries = new AtomicLong(); + private final AtomicLong totalSizeOnDisk = new AtomicLong(); + private final AtomicLong totalFiles = new AtomicLong(); + private final AtomicLong totalWals = new AtomicLong(); + private final AtomicLong totalTablets = new AtomicLong(); + private final AtomicLong availableAlways = new AtomicLong(); + private final AtomicLong availableOnDemand = new AtomicLong(); + private final AtomicLong availableNever = new AtomicLong(); + private final AtomicLong totalAssignedTablets = new AtomicLong(); + private final AtomicLong totalAssignedToDeadServerTablets = new AtomicLong(); + private final AtomicLong totalHostedTablets = new AtomicLong(); + private final AtomicLong totalSuspendedTablets = new AtomicLong(); + private final AtomicLong totalUnassignedTablets = new AtomicLong(); + + public long getTotalEntries() { + return totalEntries.get(); + } + + public long getTotalSizeOnDisk() { + return totalSizeOnDisk.get(); + } + + public long getTotalFiles() { + return totalFiles.get(); + } + + public long getTotalWals() { + return totalWals.get(); + } + + public long getTotalTablets() { + return totalTablets.get(); + } + + public long getAvailableAlways() { + return availableAlways.get(); + } + + public long getAvailableOnDemand() { + return availableOnDemand.get(); + } + + public long getAvailableNever() { + return availableNever.get(); + } + + public long getTotalAssignedTablets() { + return totalAssignedTablets.get(); + } + + public long getTotalAssignedToDeadServerTablets() { + return totalAssignedToDeadServerTablets.get(); + } + + public long getTotalHostedTablets() { + return totalHostedTablets.get(); + } + + public long getTotalSuspendedTablets() { + return totalSuspendedTablets.get(); + } + + public long getTotalUnassignedTablets() { + return totalUnassignedTablets.get(); + } + + public void addTablet(TabletInformation info) { + totalEntries.addAndGet(info.getEstimatedEntries()); + totalSizeOnDisk.addAndGet(info.getEstimatedSize()); + totalFiles.addAndGet(info.getNumFiles()); + totalWals.addAndGet(info.getNumWalLogs()); + totalTablets.addAndGet(1); + switch (info.getTabletAvailability()) { + case HOSTED: + availableAlways.addAndGet(1); + break; + case ONDEMAND: + availableOnDemand.addAndGet(1); + break; + case UNHOSTED: + availableNever.addAndGet(1); + break; + default: + throw new RuntimeException("Error processing TabletInformation, unknown availability: " + + info.getTabletAvailability()); + } + TabletState state = TabletState.valueOf(info.getTabletState()); + switch (state) { + case ASSIGNED: + totalAssignedTablets.addAndGet(1); + break; + case ASSIGNED_TO_DEAD_SERVER: + totalAssignedToDeadServerTablets.addAndGet(1); + break; + case HOSTED: + totalHostedTablets.addAndGet(1); + break; + case SUSPENDED: + totalSuspendedTablets.addAndGet(1); + break; + case UNASSIGNED: + totalUnassignedTablets.addAndGet(1); + break; + default: + throw new RuntimeException( + "Error processing TabletInformation, unknown state: " + info.getTabletState()); + } + } + } + private static final Logger LOG = LoggerFactory.getLogger(SystemInformation.class); private final DistributionStatisticConfig DSC = @@ -79,6 +270,10 @@ public class SystemInformation { private final Map> rgTServerMetrics = new ConcurrentHashMap<>(); + // Table Information + private final Map tables = new ConcurrentHashMap<>(); + private final Map> tablets = new ConcurrentHashMap<>(); + public SystemInformation(Cache allMetrics) { this.allMetrics = allMetrics; } @@ -162,6 +357,12 @@ public void processResponse(final ServerId server, final MetricResponse response } + public void processTabletInformation(String tableName, TabletInformation info) { + final SanitizedTabletInformation sti = new SanitizedTabletInformation(info); + tablets.computeIfAbsent(tableName, (t) -> new ArrayList<>()).add(sti); + tables.computeIfAbsent(tableName, (t) -> new TableSummary()).addTablet(sti); + } + public void processError(ServerId server) { problemHosts.add(server); } @@ -221,4 +422,12 @@ public Map getTServerAllMetricSummary() { return this.totalTServerMetrics; } + public Map getTables() { + return this.tables; + } + + public List getTablets(String table) { + return this.tablets.get(table); + } + } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/TabletIdSerializer.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/TabletIdSerializer.java new file mode 100644 index 00000000000..5a77fa32270 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/TabletIdSerializer.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor.next.serializers; + +import java.io.IOException; + +import org.apache.accumulo.core.data.TabletId; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class TabletIdSerializer extends JsonSerializer { + + @Override + public void serialize(TabletId value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeString(value.toString()); + } + +} From 9ea2f35b0f8676666f12e2d6c128a383b46660f9 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Thu, 7 Nov 2024 18:54:49 +0000 Subject: [PATCH 17/22] Moved compactions code, added /metrics/deployment --- .../monitor/next/InformationFetcher.java | 50 ++++----- .../accumulo/monitor/next/NewMonitor.java | 47 ++++---- .../monitor/next/SystemInformation.java | 103 +++++++++++++++++- 3 files changed, 144 insertions(+), 56 deletions(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index 6d53d8c6801..544393d26fb 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -28,8 +28,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -59,6 +57,7 @@ import org.apache.accumulo.core.util.UtilWaitThread; import org.apache.accumulo.core.util.threads.ThreadPools; import org.apache.accumulo.core.util.threads.ThreadPools.ExecutionError; +import org.apache.accumulo.monitor.next.SystemInformation.ProcessSummary; import org.apache.accumulo.monitor.next.SystemInformation.TableSummary; import org.apache.accumulo.server.ServerContext; import org.apache.thrift.transport.TTransportException; @@ -112,7 +111,7 @@ public Set getVolumes() { } } - private static class GcServerId extends ServerId { + static class GcServerId extends ServerId { private GcServerId(String resourceGroup, String host, int port) { // TODO: This is a little wonky, Type.GC does not exist in the public API, super(ServerId.Type.MANAGER, resourceGroup, host, port); @@ -180,6 +179,12 @@ private class CompactionListFetcher implements Runnable { private final String coordinatorMissingMsg = "Error getting the compaction coordinator client. Check that the Manager is running."; + private final SystemInformation summary; + + public CompactionListFetcher(SystemInformation summary) { + this.summary = summary; + } + // Copied from Monitor private TExternalCompactionList getExternalCompactions() { Set managers = ctx.instanceOperations().getServers(ServerId.Type.MANAGER); @@ -210,20 +215,7 @@ private TExternalCompactionList getExternalCompactions() { public void run() { try { TExternalCompactionList running = getExternalCompactions(); - // Create an index into the running compaction list that is - // sorted by duration, meaning earliest start time first. This - // will allow us to show topN longest running compactions. - Map timeSortedEcids = new TreeMap<>(); - if (running.getCompactions() != null) { - running.getCompactions().forEach((ecid, extComp) -> { - if (extComp.getUpdates() != null && !extComp.getUpdates().isEmpty()) { - Set orderedUpdateTimes = new TreeSet<>(extComp.getUpdates().keySet()); - timeSortedEcids.put(orderedUpdateTimes.iterator().next(), ecid); - } - }); - } - runningCompactions.set(running.getCompactions()); - runningCompactionsDurationIndex.set(timeSortedEcids); + summary.processExternalCompactionList(running); } catch (Exception e) { LOG.error("Error gathering running compaction information", e); } @@ -239,14 +231,8 @@ public void run() { private final Supplier connectionCount; private final AtomicBoolean newConnectionEvent = new AtomicBoolean(false); private final Cache allMetrics; - private final AtomicReference summaryRef = new AtomicReference<>(); - private final AtomicReference> runningCompactions = - new AtomicReference<>(); - private final AtomicReference> runningCompactionsDurationIndex = - new AtomicReference<>(); - public InformationFetcher(ServerContext ctx, Supplier connectionCount) { this.ctx = ctx; this.connectionCount = connectionCount; @@ -312,7 +298,7 @@ public void run() { } // Fetch external compaction information from the Manager - futures.add(this.pool.submit(new CompactionListFetcher())); + futures.add(this.pool.submit(new CompactionListFetcher(summary))); // Fetch Tablet / Tablet information from the metadata table for (String tName : this.ctx.tableOperations().list()) { @@ -337,6 +323,9 @@ public void run() { UtilWaitThread.sleep(3_000); } } + + summary.finish(); + refreshTime = NanoTime.now(); LOG.info("Finished fetching metrics from servers"); LOG.info( @@ -478,13 +467,7 @@ public Map getTabletServerAllMetricSummary } public Collection getCompactions(int topN) { - List results = new ArrayList<>(); - Map compactions = runningCompactions.get(); - Iterator ecids = runningCompactionsDurationIndex.get().values().iterator(); - for (int i = 0; i < topN && ecids.hasNext(); i++) { - results.add(compactions.get(ecids.next())); - } - return results; + return getSummary().getCompactions(topN); } public Map getTables() { @@ -494,4 +477,9 @@ public Map getTables() { public List getTablets(String tableName) { return getSummary().getTablets(tableName); } + + public Map> getDeploymentOverview() { + return getSummary().getDeploymentOverview(); + } + } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java index ec23be012d1..656c2761d3a 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java @@ -69,7 +69,7 @@ public class NewMonitor implements Connection.Listener { private final String hostname; private final boolean secure; private final ConnectionStatistics connStats; - private final InformationFetcher metrics; + private final InformationFetcher fetcher; public NewMonitor(ServerContext ctx, String hostname) { this.ctx = ctx; @@ -78,7 +78,7 @@ public NewMonitor(ServerContext ctx, String hostname) { .allMatch(s -> s != null && !s.isEmpty()); this.connStats = new ConnectionStatistics(); - this.metrics = new InformationFetcher(ctx, connStats::getConnections); + this.fetcher = new InformationFetcher(ctx, connStats::getConnections); } @SuppressFBWarnings(value = "UNENCRYPTED_SERVER_SOCKET", @@ -92,7 +92,7 @@ public void start() throws IOException { ss.close(); final int httpPort = ss.getLocalPort(); - Threads.createThread("Metric Fetcher Thread", metrics).start(); + Threads.createThread("Metric Fetcher Thread", fetcher).start(); Javalin.create(config -> { // TODO Make dev logging and route overview configurable based on property @@ -175,35 +175,36 @@ public void start() throws IOException { }); } }).get("/stats", ctx -> ctx.result(connStats.dump())) - .get("/metrics", ctx -> ctx.json(metrics.getAll())) - .get("/metrics/instance", ctx -> ctx.json(metrics.getInstanceSummary())) - .get("/metrics/groups", ctx -> ctx.json(metrics.getResourceGroups())) - .get("/metrics/manager", ctx -> ctx.json(metrics.getManager())) - .get("/metrics/gc", ctx -> ctx.json(metrics.getGarbageCollector())) - .get("/metrics/compactors/summary", ctx -> ctx.json(metrics.getCompactorAllMetricSummary())) + .get("/metrics", ctx -> ctx.json(fetcher.getAll())) + .get("/metrics/instance", ctx -> ctx.json(fetcher.getInstanceSummary())) + .get("/metrics/groups", ctx -> ctx.json(fetcher.getResourceGroups())) + .get("/metrics/manager", ctx -> ctx.json(fetcher.getManager())) + .get("/metrics/gc", ctx -> ctx.json(fetcher.getGarbageCollector())) + .get("/metrics/compactors/summary", ctx -> ctx.json(fetcher.getCompactorAllMetricSummary())) .get("/metrics/compactors/summary/{group}", - ctx -> ctx.json(metrics.getCompactorResourceGroupMetricSummary(ctx.pathParam("group")))) + ctx -> ctx.json(fetcher.getCompactorResourceGroupMetricSummary(ctx.pathParam("group")))) .get("/metrics/compactors/detail/{group}", - ctx -> ctx.json(metrics.getCompactors(ctx.pathParam("group")))) - .get("/metrics/sservers/summary", ctx -> ctx.json(metrics.getScanServerAllMetricSummary())) + ctx -> ctx.json(fetcher.getCompactors(ctx.pathParam("group")))) + .get("/metrics/sservers/summary", ctx -> ctx.json(fetcher.getScanServerAllMetricSummary())) .get("/metrics/sservers/summary/{group}", ctx -> ctx - .json(metrics.getScanServerResourceGroupMetricSummary(ctx.pathParam("group")))) + .json(fetcher.getScanServerResourceGroupMetricSummary(ctx.pathParam("group")))) .get("/metrics/sservers/detail/{group}", - ctx -> ctx.json(metrics.getScanServers(ctx.pathParam("group")))) + ctx -> ctx.json(fetcher.getScanServers(ctx.pathParam("group")))) .get("/metrics/tservers/summary", - ctx -> ctx.json(metrics.getTabletServerAllMetricSummary())) + ctx -> ctx.json(fetcher.getTabletServerAllMetricSummary())) .get("/metrics/tservers/summary/{group}", ctx -> ctx - .json(metrics.getTabletServerResourceGroupMetricSummary(ctx.pathParam("group")))) + .json(fetcher.getTabletServerResourceGroupMetricSummary(ctx.pathParam("group")))) .get("/metrics/tservers/detail/{group}", - ctx -> ctx.json(metrics.getTabletServers(ctx.pathParam("group")))) - .get("/metrics/problems", ctx -> ctx.json(metrics.getProblemHosts())) - .get("/metrics/compactions", ctx -> ctx.json(metrics.getCompactions(25))) + ctx -> ctx.json(fetcher.getTabletServers(ctx.pathParam("group")))) + .get("/metrics/problems", ctx -> ctx.json(fetcher.getProblemHosts())) + .get("/metrics/compactions", ctx -> ctx.json(fetcher.getCompactions(25))) .get("/metrics/compactions/{num}", - ctx -> ctx.json(metrics.getCompactions(Integer.parseInt(ctx.pathParam("num"))))) - .get("/metrics/tables", ctx -> ctx.json(metrics.getTables())) - .get("/metrics/tables/{name}", ctx -> ctx.json(metrics.getTablets(ctx.pathParam("name")))) + ctx -> ctx.json(fetcher.getCompactions(Integer.parseInt(ctx.pathParam("num"))))) + .get("/metrics/tables", ctx -> ctx.json(fetcher.getTables())) + .get("/metrics/tables/{name}", ctx -> ctx.json(fetcher.getTablets(ctx.pathParam("name")))) + .get("/metrics/deployment", ctx -> ctx.json(fetcher.getDeploymentOverview())) .exception(NotFoundException.class, (e, ctx) -> ctx.status(404)).start(); LOG.info("New Monitor listening on port: {}", httpPort); @@ -212,7 +213,7 @@ public void start() throws IOException { @Override public void onOpened(Connection connection) { LOG.info("New connection event"); - metrics.newConnectionEvent(); + fetcher.newConnectionEvent(); } @Override diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java index 97dae79c54b..ed2dd46774a 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java @@ -20,11 +20,16 @@ import java.time.Duration; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -32,12 +37,15 @@ import org.apache.accumulo.core.client.admin.TabletAvailability; import org.apache.accumulo.core.client.admin.TabletInformation; import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; +import org.apache.accumulo.core.compaction.thrift.TExternalCompactionList; import org.apache.accumulo.core.data.TabletId; import org.apache.accumulo.core.dataImpl.KeyExtent; import org.apache.accumulo.core.dataImpl.TabletIdImpl; import org.apache.accumulo.core.metadata.TabletState; import org.apache.accumulo.core.metrics.flatbuffers.FMetric; import org.apache.accumulo.core.metrics.thrift.MetricResponse; +import org.apache.accumulo.monitor.next.InformationFetcher.GcServerId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -105,8 +113,7 @@ public String getTabletState() { @Override public Optional getLocation() { - // TODO Auto-generated method stub - return Optional.empty(); + return tabletInfo.getLocation(); } @Override @@ -233,6 +240,38 @@ public void addTablet(TabletInformation info) { } } + public static class ProcessSummary { + private long configured = 0; + private long responded = 0; + private Set notResponded = new HashSet<>(); + + public void addResponded() { + configured++; + responded++; + } + + public void addNotResponded(ServerId server) { + notResponded.add(server.getHost() + ":" + server.getPort()); + } + + public long getConfigured() { + return this.configured; + } + + public long getResponded() { + return this.responded; + } + + public long getNotResponded() { + return this.notResponded.size(); + } + + public Set getNotRespondedHosts() { + return this.notResponded; + } + + } + private static final Logger LOG = LoggerFactory.getLogger(SystemInformation.class); private final DistributionStatisticConfig DSC = @@ -270,10 +309,19 @@ public void addTablet(TabletInformation info) { private final Map> rgTServerMetrics = new ConcurrentHashMap<>(); + // Compaction Information + private final AtomicReference> runningCompactions = + new AtomicReference<>(); + private final AtomicReference> runningCompactionsDurationIndex = + new AtomicReference<>(); + // Table Information private final Map tables = new ConcurrentHashMap<>(); private final Map> tablets = new ConcurrentHashMap<>(); + // Deployment Overview + private final Map> deployment = new HashMap<>(); + public SystemInformation(Cache allMetrics) { this.allMetrics = allMetrics; } @@ -357,6 +405,23 @@ public void processResponse(final ServerId server, final MetricResponse response } + public void processExternalCompactionList(TExternalCompactionList running) { + // Create an index into the running compaction list that is + // sorted by duration, meaning earliest start time first. This + // will allow us to show topN longest running compactions. + Map timeSortedEcids = new TreeMap<>(); + if (running.getCompactions() != null) { + running.getCompactions().forEach((ecid, extComp) -> { + if (extComp.getUpdates() != null && !extComp.getUpdates().isEmpty()) { + Set orderedUpdateTimes = new TreeSet<>(extComp.getUpdates().keySet()); + timeSortedEcids.put(orderedUpdateTimes.iterator().next(), ecid); + } + }); + } + runningCompactions.set(running.getCompactions()); + runningCompactionsDurationIndex.set(timeSortedEcids); + } + public void processTabletInformation(String tableName, TabletInformation info) { final SanitizedTabletInformation sti = new SanitizedTabletInformation(info); tablets.computeIfAbsent(tableName, (t) -> new ArrayList<>()).add(sti); @@ -367,6 +432,26 @@ public void processError(ServerId server) { problemHosts.add(server); } + public void finish() { + // Iterate over the metrics + allMetrics.asMap().keySet().forEach(serverId -> { + String typeName = serverId.getType().name(); + if (serverId instanceof GcServerId) { + typeName = "GC"; + } + deployment.computeIfAbsent(serverId.getResourceGroup(), g -> new HashMap<>()) + .computeIfAbsent(typeName, t -> new ProcessSummary()).addResponded(); + }); + problemHosts.forEach(serverId -> { + String typeName = serverId.getType().name(); + if (serverId instanceof GcServerId) { + typeName = "GC"; + } + deployment.computeIfAbsent(serverId.getResourceGroup(), g -> new HashMap<>()) + .computeIfAbsent(typeName, t -> new ProcessSummary()).addNotResponded(serverId); + }); + } + public Set getResourceGroups() { return this.resourceGroups; } @@ -422,6 +507,16 @@ public Map getTServerAllMetricSummary() { return this.totalTServerMetrics; } + public Collection getCompactions(int topN) { + List results = new ArrayList<>(); + Map compactions = runningCompactions.get(); + Iterator ecids = runningCompactionsDurationIndex.get().values().iterator(); + for (int i = 0; i < topN && ecids.hasNext(); i++) { + results.add(compactions.get(ecids.next())); + } + return results; + } + public Map getTables() { return this.tables; } @@ -430,4 +525,8 @@ public List getTablets(String table) { return this.tablets.get(table); } + public Map> getDeploymentOverview() { + return this.deployment; + } + } From e7b1bb3eb5f04883fc99c8fd8961c7d3ca776555 Mon Sep 17 00:00:00 2001 From: "Dom G." Date: Sat, 9 Nov 2024 09:51:39 -0500 Subject: [PATCH 18/22] Initial commit of React frontend (#48) --- server/monitor/.gitignore | 7 + server/monitor/pom.xml | 60 + .../accumulo/monitor/next/NewMonitor.java | 5 + .../org/apache/accumulo/newmonitor/README.md | 27 + .../accumulo/newmonitor/eslint.config.js | 63 + .../org/apache/accumulo/newmonitor/index.html | 33 + .../accumulo/newmonitor/package-lock.json | 5129 +++++++++++++++++ .../apache/accumulo/newmonitor/package.json | 34 + .../accumulo/newmonitor/public/favicon.png | Bin 0 -> 2195 bytes .../apache/accumulo/newmonitor/src/App.css | 18 + .../apache/accumulo/newmonitor/src/App.tsx | 101 + .../org/apache/accumulo/newmonitor/src/api.ts | 181 + .../newmonitor/src/assets/accumulo-avatar.png | Bin 0 -> 1601 bytes .../newmonitor/src/assets/accumulo-logo.png | Bin 0 -> 11427 bytes .../newmonitor/src/components/HomePage.tsx | 163 + .../src/components/ResourceGroupPage.tsx | 164 + .../components/ResourceGroupsOverviewPage.tsx | 107 + .../src/components/ServerOverviewPage.tsx | 76 + .../newmonitor/src/components/ServerPage.tsx | 83 + .../newmonitor/src/components/TablesPage.tsx | 74 + .../apache/accumulo/newmonitor/src/index.css | 59 + .../apache/accumulo/newmonitor/src/main.tsx | 32 + .../apache/accumulo/newmonitor/src/types.ts | 127 + .../accumulo/newmonitor/src/vite-env.d.ts | 20 + .../accumulo/newmonitor/tsconfig.app.json | 25 + .../newmonitor/tsconfig.app.tsbuildinfo | 1 + .../apache/accumulo/newmonitor/tsconfig.json | 7 + .../accumulo/newmonitor/tsconfig.node.json | 23 + .../newmonitor/tsconfig.node.tsbuildinfo | 1 + .../apache/accumulo/newmonitor/vite.config.ts | 38 + 30 files changed, 6658 insertions(+) create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/README.md create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/eslint.config.js create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/index.html create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package-lock.json create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package.json create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/public/favicon.png create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.css create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/api.ts create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/assets/accumulo-avatar.png create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/assets/accumulo-logo.png create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/HomePage.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupPage.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupsOverviewPage.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerOverviewPage.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerPage.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/TablesPage.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/index.css create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/main.tsx create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/types.ts create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/vite-env.d.ts create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.json create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.tsbuildinfo create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.json create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.json create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.tsbuildinfo create mode 100644 server/monitor/src/main/resources/org/apache/accumulo/newmonitor/vite.config.ts diff --git a/server/monitor/.gitignore b/server/monitor/.gitignore index 55d7f58f9d5..0c62666f0ec 100644 --- a/server/monitor/.gitignore +++ b/server/monitor/.gitignore @@ -34,3 +34,10 @@ /nb-configuration.xml /.vscode/ /.factorypath + +# Frontend ignores +node_modules +dist +logs +*.log +npm-debug.log* \ No newline at end of file diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index b0ae039cc12..a1c83b6a01c 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -255,9 +255,16 @@
${rootlocation}/src/build/license-header.txt
src/main/resources/org/apache/accumulo/monitor/resources/external/**/* + src/main/resources/org/apache/accumulo/newmonitor/dist/**/* + src/main/resources/org/apache/accumulo/newmonitor/node_modules/**/* + + SLASHSTAR_STYLE + SLASHSTAR_STYLE + NO_STYLE + @@ -266,9 +273,62 @@ src/main/resources/org/apache/accumulo/monitor/resources/external/**/* + src/main/resources/org/apache/accumulo/newmonitor/dist/**/* + src/main/resources/org/apache/accumulo/newmonitor/node_modules/**/* + src/main/resources/org/apache/accumulo/newmonitor/**/*.tsbuildinfo + + com.github.eirslett + frontend-maven-plugin + 1.15.1 + + src/main/resources/org/apache/accumulo/newmonitor + v22.11.0 + 10.9.0 + target + + + + install node and npm + + install-node-and-npm + + generate-sources + + + npm-install + + npm + + generate-sources + + install + + + + npm-run-build + + npm + + generate-sources + + run build + + + + npm-run-lint + + npm + + validate + + run lint + + + + diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java index 656c2761d3a..13aeab0e331 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java @@ -51,6 +51,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.javalin.Javalin; +import io.javalin.http.staticfiles.Location; import io.javalin.json.JavalinJackson; import io.javalin.security.RouteRole; import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; @@ -95,6 +96,10 @@ public void start() throws IOException { Threads.createThread("Metric Fetcher Thread", fetcher).start(); Javalin.create(config -> { + config.staticFiles.add("org/apache/accumulo/newmonitor/dist", Location.CLASSPATH); + config.spaRoot.addFile("/", "org/apache/accumulo/newmonitor/dist/index.html", + Location.CLASSPATH); + // TODO Make dev logging and route overview configurable based on property // They are useful for development and debugging, but should probably not // be enabled for normal use. diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/README.md b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/README.md new file mode 100644 index 00000000000..bcf6cba4749 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/README.md @@ -0,0 +1,27 @@ + + +## NPM Commands + +- `npm run dev`: Starts the development server with hot module replacement. +- `npm run build`: Builds the frontend for production and outputs to `./dist`. +- `npm run lint`: Runs ESLint to analyze the code for potential errors. +- `npm run preview`: Serves the production build locally for preview. \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/eslint.config.js b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/eslint.config.js new file mode 100644 index 00000000000..72fb15ef14c --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/eslint.config.js @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import react from 'eslint-plugin-react' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [ + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + ], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, + settings: { + react: { + version: '18.3', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + react, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, + }, +) \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/index.html b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/index.html new file mode 100644 index 00000000000..bce44300d1c --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/index.html @@ -0,0 +1,33 @@ + + + + + + + + Accumulo Monitor + + +
+ + + diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package-lock.json b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package-lock.json new file mode 100644 index 00000000000..8fd018820c9 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package-lock.json @@ -0,0 +1,5129 @@ +{ + "name": "monitor", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "monitor", + "version": "0.0.0", + "dependencies": { + "bootstrap": "^5.3.3", + "react": "^18.3.1", + "react-bootstrap": "^2.10.5", + "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/react": "^18.3.12", + "@types/react-bootstrap": "^0.32.37", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "eslint": "^9.13.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.11.0", + "vite": "^5.4.10" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", + "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz", + "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.6.tgz", + "integrity": "sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.8.0.tgz", + "integrity": "sha512-xJEOXUOTmT4FngTmhdjKFRrVVF0hwCLNPdatLCHkyS4dkiSK12cEu1Y0fjxktjJrdst9jJIc5J6ihMJCoWEN/g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz", + "integrity": "sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz", + "integrity": "sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz", + "integrity": "sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz", + "integrity": "sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz", + "integrity": "sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz", + "integrity": "sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz", + "integrity": "sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz", + "integrity": "sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz", + "integrity": "sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz", + "integrity": "sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz", + "integrity": "sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz", + "integrity": "sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz", + "integrity": "sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz", + "integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz", + "integrity": "sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz", + "integrity": "sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz", + "integrity": "sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz", + "integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-bootstrap": { + "version": "0.32.37", + "resolved": "https://registry.npmjs.org/@types/react-bootstrap/-/react-bootstrap-0.32.37.tgz", + "integrity": "sha512-CVHj++uxsj1pRnM3RQ/NAXcWj+JwJZ3MqQ28sS1OQUD1sI2gRlbeAjRT+ak2nuwL+CY+gtnIsMaIDq0RNfN0PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.13.0.tgz", + "integrity": "sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.13.0", + "@typescript-eslint/type-utils": "8.13.0", + "@typescript-eslint/utils": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.13.0.tgz", + "integrity": "sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.13.0", + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/typescript-estree": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.13.0.tgz", + "integrity": "sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.13.0.tgz", + "integrity": "sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.13.0", + "@typescript-eslint/utils": "8.13.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz", + "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.13.0.tgz", + "integrity": "sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.13.0.tgz", + "integrity": "sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.13.0", + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/typescript-estree": "8.13.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz", + "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.13.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001677", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", + "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz", + "integrity": "sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", + "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.3", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", + "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.7.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.14.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.0", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.1.0", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", + "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz", + "integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", + "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "license": "MIT", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.5.tgz", + "integrity": "sha512-XueAOEn64RRkZ0s6yzUTdpFtdUXs5L5491QU//8ZcODKJNDLt/r01tNyriZccjgRImH1REynUc9pqjiRMpDLWQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.9", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.20.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.4.tgz", + "integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.4", + "@rollup/rollup-android-arm64": "4.24.4", + "@rollup/rollup-darwin-arm64": "4.24.4", + "@rollup/rollup-darwin-x64": "4.24.4", + "@rollup/rollup-freebsd-arm64": "4.24.4", + "@rollup/rollup-freebsd-x64": "4.24.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.4", + "@rollup/rollup-linux-arm-musleabihf": "4.24.4", + "@rollup/rollup-linux-arm64-gnu": "4.24.4", + "@rollup/rollup-linux-arm64-musl": "4.24.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.4", + "@rollup/rollup-linux-riscv64-gnu": "4.24.4", + "@rollup/rollup-linux-s390x-gnu": "4.24.4", + "@rollup/rollup-linux-x64-gnu": "4.24.4", + "@rollup/rollup-linux-x64-musl": "4.24.4", + "@rollup/rollup-win32-arm64-msvc": "4.24.4", + "@rollup/rollup-win32-ia32-msvc": "4.24.4", + "@rollup/rollup-win32-x64-msvc": "4.24.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", + "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.13.0.tgz", + "integrity": "sha512-vIMpDRJrQd70au2G8w34mPps0ezFSPMEX4pXkTzUkrNbRX+36ais2ksGWN0esZL+ZMaFJEneOBHzCgSqle7DHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.13.0", + "@typescript-eslint/parser": "8.13.0", + "@typescript-eslint/utils": "8.13.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "license": "MIT", + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package.json b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package.json new file mode 100644 index 00000000000..390421bbb19 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/package.json @@ -0,0 +1,34 @@ +{ + "name": "monitor", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "bootstrap": "^5.3.3", + "react": "^18.3.1", + "react-bootstrap": "^2.10.5", + "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/react": "^18.3.12", + "@types/react-bootstrap": "^0.32.37", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "eslint": "^9.13.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.11.0", + "vite": "^5.4.10" + } +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/public/favicon.png b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a632dab9cb3e3ad9ed2e6abf3f8552988c443b38 GIT binary patch literal 2195 zcmV;E2yFL>P)4Tx0C)j~RL^S@K@|QrZmG~B2wH0nvUrdpNm;9CMbtL^5n^i$+aIn^?(HA4aZWV5ov6ELTdbo0FI&wK{O>*+w4vx20?>!`FrQsdJlnHR>OPy zcd~b_n$otK2Za4V;76L-DzNVtaSB-y0*E}{p()372;bw_^6ZZ}PI-92wGS&j#91PI zKs7DSe@(bk%_Y-7gGe}(^>I=@oY#w#*Bu9GZf3^F5WP>3rn}7Ut74&?PWBFvy`A)a zPP5)V!Xd&78LdA?xQ(9mjMYElVd13a#D+Z_7&Y|xU=_C-srWU*6kiZcC!$nw*)9$7 zn6CX+@=AhmkT}X@VSsa5NKe;HZuq)~1$`#h6R+ZTR#D-3j}vF!)ZOnz+5)dI4jl{{ z44Mr{P!L4~VVJN`K!!XTF*LGrKO?IK8z<8w`3e3jI8lUGNUta*C8 zn(P`s>{pjD=7Kek#B;Fw@hxAK%$F&Q6vg9J^Xf~4by_hu-=A!MJ3Znq&n~srbFGPs zH&&aMXZ>nO`|hf|ljc?VPhR!${AbO?W8x_>CU%PFA&Hm8F7cAsOREdwU~R_;ot1_u z(ruCYB-LPGn!NQdT|ZlRy+(fw^-+`=%+gee_kY4FWHg<*4sZI8+sFJD270UUORdLHO0nA4V) z%{fwsET5CQ>B?eK%uw4yQc~9?*JVo2}ze(;aRcp*ceL#HUJSllrgm5wQKR zQu+C;QrUh^8rFfA`ftFz{YAidi-`aL010qNS#tmY3ljhU3ljkVnw%H_00m1)L_t(o z38h!ZOQcH>uTN*pt^s|Dam9dZcqm>xi3ibxhkXY@kK#%2&+tDGysaJuLGUdq9u#~R zBPu4$n6qNej`r(ct@Y6(Gsa@R=~P|0s=G;PxV*en!^6Ys{QMk4hMAcewY0P(!%w#h z3kzy)ZqD8h9z#Pz%oCG7q~Yr7itg|4$?0@r$l!1|=;Go+hM#Uh!^el&*mPYk7ipz= zY)wv1DhLoCA5Te1Nu-4$%d+U^=7wfxXL%eP9L#fAdkNJPhK7>0xVWgo!^0^$I@(4c0warVZ*OU5XNMjiAKkPt5WRMvo}S3p*O%hr;wT^> zfbm2*3=R(u>GbrJEIsK_Efd)*mv=6_M#~{IUPFSU`}_NZxPVrkaAgjfKRi6JMI$34 zg}hrUxR`Vt92~GOOdkmX)_(u~m-_ntBONOg6cj}M{{93eo&dpicX!9W&&kQ5l9Ceo z`Oi;^`71^{RU2CW5EBw(9OMH70}9{4;eqC>l=e5bI!IJyWhLD6rLUr*Lanc_Gfgcx zw?{`us=vQq4Gs<}M`2+hZEkJS^3pPW3;o6#!i0^DjnvxO%8EjhA6Y*rFd&dqRa5a~ z0G>mNA$WD}Q%_G1wY9ZTYHBLg*Vpr2tSv7ubJis%Cu{XIpb!lX4ycop6P9p#dRk>> zX0qnh)zxZsb(QC}U~C~+$=e4U0JN^DsbLyA-4qHN;Oy*-Ee@Ult`P94)9J@S1~umA z=lMgwyu6&gfB%jyufxx;eF#(lYD#HoDfi)PEdckij)Es~#MnA6+#oG2jqt%02qpkF zAb8#bS}_*VtgM(5KwoG*r{N#MGMSf`M;#p<9J$&RoOUJwA>^qw@sP3tW|qkuTDuPL zq@f@f3qUZFn9RS`N(BU-nEd4)cLAQQ1Y~F;Om}y8X?uH{eFq>dwX>;>Z7QrC@w}g8@v%T8Q_GCh=?GaU0k5dHR*d5 zKp#?6TU*PF1z8sm>At&-iHTuc)CW)CCV=<}#iF61!De1nRYlp^*<7T=1>P(m?m-4j zO-<6TUro#xW!xj^4NB*!8XXRbLo-(G?d`l@ z-x1Wv$O!Yqgnd=fef!MU*C^WC+hc)?i;LO!9;JU{5;WfeHx~%9xB_DYBLKwkc{4pD zJ^zU^rr%APCN2x`lCX(v3LqPS?+H*Su_Wsz9bC@@poHMC0GI$W(hHD~kRZ5(YygNL z=)YzK2qCbtvLaoZCivhJ8Z3b8(ZzWxlc@hVgu1%AsHv%mb3ZpXmp6IK0bie`KRF#vJAiDP zK6fl1i(C(XZbT_5DO6BUKvD^{5VqgVeYOBE5ikabAm*lz^q~}J`B-+zaZ}L3)d**` z8RaH}>+5TdS_wXL&08NrBgjD0TM33xpzWBLnBZqqMn(pOgoL;@2V0L`z>FI3o{{er5 Va0I?OO(FmQ002ovPDHLkV1knD2j2hy literal 0 HcmV?d00001 diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.css b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.css new file mode 100644 index 00000000000..ba830d1f799 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.css @@ -0,0 +1,18 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.tsx b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.tsx new file mode 100644 index 00000000000..bbe80a6cd5c --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/App.tsx @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import './App.css'; +import { Routes, Route, Link } from 'react-router-dom'; +import { Navbar, Nav, Container, NavDropdown } from 'react-bootstrap'; +import HomePage from './components/HomePage'; +import ServerOverviewPage from './components/ServerOverviewPage'; +import ServerPage from './components/ServerPage'; +import ResourceGroupPage from './components/ResourceGroupPage'; +import { InstanceMetrics, ServerType } from './types'; +import accumuloAvatar from './assets/accumulo-avatar.png'; +import { useEffect, useState } from 'react'; +import { fetchInstanceMetrics } from './api'; +import ResourceGroupsOverviewPage from './components/ResourceGroupsOverviewPage'; +import TablesPage from './components/TablesPage'; + +function App() { + const [instanceName, setInstanceName] = useState(''); + + useEffect(() => { + async function fetchAndStoreInstanceMetrics() { + const data: InstanceMetrics = await fetchInstanceMetrics(); + setInstanceName(data.instanceName); + } + void fetchAndStoreInstanceMetrics(); + }, []); + + return ( + <> + + + + accumulo + {instanceName} + + + + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} + +export default App; diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/api.ts b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/api.ts new file mode 100644 index 00000000000..36b3696917b --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/api.ts @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ + +import { + ServerData, + SummaryMetrics, + InstanceMetrics, + CompactionsMetrics, + ProblemMetrics, + HealthStatus, + Warning, + ResourceGroupDetails, + TablesMetrics, + DeploymentsMetrics, +} from './types'; + +async function fetchWithHandling( + path: string, + options?: { returnEmptyOn404?: boolean } +): Promise { + const response = await fetch(path); + + if (response.status === 404) { + if (options?.returnEmptyOn404) { + // Resource not found but should return empty data + return {} as T; + } + // Resource not found, can throw an error or return empty data + throw new Error(`Resource not found at ${path}`); + } + + if (response.status === 500) { + // Internal Server Error + throw new Error(`Server error at ${path}: ${response.statusText}`); + } + + if (!response.ok) { + // Other non-OK responses + throw new Error(`Failed to fetch ${path}: ${response.statusText}`); + } + + return await response.json() as T; +} + +export async function fetchAllMetrics(): Promise { + const path = '/metrics'; + return await fetchWithHandling(path); +} + +export async function fetchInstanceMetrics(): Promise { + const path = '/metrics/instance'; + return await fetchWithHandling(path); +} + +export async function fetchGroups(): Promise { + const path = '/metrics/groups'; + return await fetchWithHandling(path); +} + +export async function fetchManagerMetrics(): Promise { + const path = '/metrics/manager'; + return await fetchWithHandling(path); +} + +export async function fetchGCMetrics(): Promise { + const path = '/metrics/gc'; + return await fetchWithHandling(path); +} + +export async function fetchCompactorsSummary(group?: string): Promise { + const path = group ? `/metrics/compactors/summary/${group}` : '/metrics/compactors/summary'; + return await fetchWithHandling(path, { returnEmptyOn404: true }); +} + +export async function fetchCompactorsDetail(group: string): Promise { + const path = `/metrics/compactors/detail/${group}`; + return await fetchWithHandling(path, { returnEmptyOn404: true }); +} + +export async function fetchScanServerSummary(group?: string): Promise { + const path = group ? `/metrics/sservers/summary/${group}` : '/metrics/sservers/summary'; + return await fetchWithHandling(path, { returnEmptyOn404: true }); +} + +export async function fetchScanServerDetail(group: string): Promise { + const path = `/metrics/sservers/detail/${group}`; + return await fetchWithHandling(path, { returnEmptyOn404: true }); +} + +export async function fetchTabletServerSummary(group?: string): Promise { + const path = group ? `/metrics/tservers/summary/${group}` : '/metrics/tservers/summary'; + return await fetchWithHandling(path, { returnEmptyOn404: true }); +} + +export async function fetchTabletServerDetail(group: string): Promise { + const path = `/metrics/tservers/detail/${group}`; + return await fetchWithHandling(path, { returnEmptyOn404: true }); +} + +export async function fetchProblems(): Promise { + const path = '/metrics/problems'; + return await fetchWithHandling(path); +} + +export async function fetchCompactions(max?: number): Promise { + const path = max ? `/metrics/compactions/${max}` : '/metrics/compactions'; + return await fetchWithHandling(path); +} + +export async function fetchTablesMetrics(name?: string): Promise { + const path = name ? `/metrics/tables/${name}` : '/metrics/tables'; + return await fetchWithHandling(path); +} + +export async function fetchDeploymentMetrics(): Promise { + const path = '/metrics/deployment'; + return await fetchWithHandling(path); +} + +// TEST DATA FOR HOMEPAGE: + +export async function fetchHealthStatus(): Promise { + // Simulate fetching health status with a random value + const statuses: HealthStatus[] = ['healthy', 'degraded', 'unhealthy']; + const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; + return Promise.resolve(randomStatus); +} + +export async function fetchWarnings(): Promise { + // Return dummy warnings + return Promise.resolve([ + { + resourceGroup: 'default', + message: 'There are warnings in RG default', + }, + { + resourceGroup: 'scansgroup', + message: 'There are warnings in RG scansgroup', + }, + ]); +} + +export async function fetchResourceGroupDetails(): Promise { + // Return dummy resource group details + return Promise.resolve([ + { + name: 'default', + healthStatus: 'degraded', + type: 'N/A', + componentCount: 9, + }, + { + name: 'scansgroup', + healthStatus: 'healthy', + type: 'scan server', + componentCount: 5, + }, + { + name: 'group 4', + healthStatus: 'unhealthy', + type: 'tablet server', + componentCount: 2, + }, + ]); +} \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/assets/accumulo-avatar.png b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/assets/accumulo-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..55befbeaf093ab0ed2fea9cf4c4ca6247b16a665 GIT binary patch literal 1601 zcmV-H2EO@;P)F<&jhdJlh^c`g(nw4j|3I~pibwUWvswFi zH=1^ReH|K$&tBx91)Lu8^7;9>#>K@&*r3t}%t^hr%gf8qSbV-o4zRPcqrQFnrsw_f z*4WzGQWFys z&IHiJ-rk&tq+SxAFgAKKlvO$qX>m7piKl~P%+iY%1Rgko=HFEwZ?>3 zAefAWrmgMt^fc7By1J?ri+3QW1bqO(VT#ZU09$^rxVWem78bObOc(n6`LpheHB15! zl)jH2KWfEc{ey!8?Rq+hg#eng65vO(v$J7lr4a)kK75EN=H}+Y+!1Ac`t(WnK>#a2 zfOxPGkW7GJo12><7m>hmsuR1ryJ}`;Mg@aGjmgQ$&}aE>edhFCKBKJf-@iM~g?uO# zs}s1m9CG1au4&XuapK)*;bpFf3fX;X_n&~IQPL9Wi8O_8-BzIZ9e&*0+^njrt5s!X zr6*KXRjKFCpR3N!&amv`G#4^~%gZYr7q&DiL2RZ`DxT9?2Xd{gttu-kOC=>Gsf2_C zUr0<$R1Y3JP(?*W>fO6{j=~e%WFXfxDhnp3G)j6SE19jWtxct-rm8?75EDQqFE3A9 z)LhJw$|a4u)Pa8q?Eg&wfhk@>fK+L1q%tNm1wfl#!S7@YzSq>$*fI^pH8>dz5n%4^ z?d|Y1D(MV&0dxw1si`R&f$r{Zm6DPYniv9y9MqDir69QIO6ZqX$Mn)jU7KnD~@t<43 z-1v|w{9<%;RHdh<-@OG$WsQxEs<5z7<>u!423g0NO7Fx1q}Z~uGL@X19J4}prxxhx z>9N&;SQ8i?9*&;{7~tj0mwNqUt)S#@KQI-6RR;$L<7WW|po&_nXK`_{>gwuJuU@_K zjHae08wnIMFfb553y|~c>+5X5m9*F@CxY&)cUyze* zEdOUueCEETv<6XklW=m zng>|KBp~;+AlhqI6eeTg<0b*=15W>L5O51n(U7SXxgV2$zbjE`xPpv@8=I%oR%=B} z0MnoufXPe(Jd^%t|J4B3m9fl9jJ2f?I0bpY?Picv#en9Z5Rm73s$6Z!?YpV0+vfTU>vgP!#UI~v00000NkvXXu0mjf&A0)4 literal 0 HcmV?d00001 diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/assets/accumulo-logo.png b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/assets/accumulo-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5b0f6b434571b37da9a86d9d38d09297b4363fac GIT binary patch literal 11427 zcmZvi1yoeg+wO-3rBq}nX{13yq`Q&sMp`6>5J_o~mX>Y?q`N`7OF+6oNDU7&LfgX5C{ZEPF7M40zt9{|2Dux2Y*Yrv(G^w z$RqD1Bvj-iBq&v!94z14T0kIfa}lwF9|&{_g4=c%GQUw`SC<)KC+f<6qm9GkrWXDs zXPaGNHy{~HDGR@L{gjllo2)NN3}8dA|6Hh=rgB5-OH? z^-@^49ocQ>;_g;Cr--1@*`y?J@+Rhy;r1o=BvY*V1SR)3VPdo=U!6tgx~?BYJzoCV z6P*hekf5|jDq8p{NqK;TVqc@Vh$Z(}b_81_ieqUCmQjyEm%Gc4uNL%PW>5sgVom7On7uA6s~LRo=m? zAnDcBoPW{08-2fIe6ys9@$e#KaD$>cR-SO)EXpfVwa1XLAGwFLuRH8A(BjJU{n6dF zZSVp9isQX$ zW2FzrgsT&jy;*(hgyNb04@tPNrApwC>A&wXSy-yrFB|W+&k3#%lJxiHTFApvddXdv zg{uCR+AQ1V>FKjR*iX}qG9STbXr;S-XZ#2}yNoLIvBQum)Wd6(-ma`WJ`JME{o1od z;57T7|AX%H2S~2-fOd~_UhLq2e_^~($y4Q1=+1I@f8l%L36~f4f#t3r^lw&7kbWn3 z*Al)6G<3*X7t`PC|GBFGb57BJbE5cRun{l%njV?x2O2zFyHMJx%FZD%KrAz!>}>Jg zNOw|?q)aVB{JloOP&3onvxGX^NW;dev8(Ly%W@>GTMXlSJ8oga_uQ=pL6KW9eIK-+ z&395F`N|t$f}@)%$VfsS5PvdT;IZHtESRjWGX(PZ8R8cSlAKBeflxx^B*iq`=Jvfk zVfs@w_n%H{ZTl1G2H6=$I&n9qy0DaxY3o{YJEO$EqZ3(DdLw7$Rgb-e?~6V#Fm@8m zDZqnbz9_KUP4Mhjzo3l!lFyneN9po(OUicZT`F%aZJW8!?c})g!@bIRlb|=;-KUlar_s(y?cHiGx8pEMMGD{`51UP@x(c8oo+;TToco?@_`KS`S-A{xg&m zP+7^TyNQV&u)Vh@p|1X{TF>tH2O)C2j@4IxMl;x(>|H*7Y}c`oG_*7Py+<952`;_@ z<2B()v50@?;zD3wM;Z|kF}CBPTeiQq*SWDlOF~L&v9;C_^muIVte~uH)WeXQq#J|6 z%eS$SUqeHqkE!$WlE}l;@VkhQFgCix?++O0=&yKqzV!4+WBOATku5GRj#+cV_|tUD z!n#c_R|4=<)YVbKx~Yq>pFMlFy|dHa?0%x6rWSKHmz70@8X(5a!y}%j#`sQ}%CiVH zFnZgmVddm1|Mqj^Dza~Xzuf8Rsfnqn>A+#w>(EJtv-5N6ry(1*p4Qg*cXxN+97g18 zpGEbqx;7C*XE$(y#1R*mnfdMkH2)jJ3T))b#c)^V(S^7 zm!B!6Z}2^bd=SXe7}*wawF1cH^QidL;?5Xy+mk(lqZAESec#(WtZK z{wvlR97cQeM;xoA55Di^!eA`B>0Z0t9dCeH=|pvQ!`Lk)Tq|3m#h9NFN@~q>gI_q! z#L|mRLqUv5725Z9KAv-exj$*LwoJNB?#v@DCP-AzxT;lMgQ1X26mhJb(^*)aam<*6}{soQPtG!{;v<# zYkQRaa8~9chL37HQ%<%1@xQMTUt!XY+-1rknWM_0Q%<@5aUtN(%F4=jB(HVS~I8#F~~a=TU%8D2zZrnNAR36JO~Hs;q#mPJMkRMGMW?^F2&LnM*M ztqI1hPhUx|>xHZ~702%8EZ> zv&Q0V*ceU;t9MDKiN?$xXJCGygF*voXFjDq_jnSU4;_Gs;fHnS?noK%v8^4sja10n z&Bhz_wNbcHGG@N=k&<(X9$f6C)@Pr6LfNV5=KQBCf};u&q1Bn z5dpDi_$rk-Mc1fIcsAh+K6TNSywx*3oyyjwcJ$j*c2S>Ny^?YrmhJuhpedW- zA|72HK2T?_udh`M4TqV`;WW_$zrjT4493eTE5mbbdJ*_7dqbA?DLyI^s6NrMsJJrr zY)mEb<+HZ127~hX{Yu9t5z?ziZYFiw6^~|T>%VhZ;etgAer#-P%-6HQS+W@_2Zm?s z3knKEH8r0but5>86iOt@(za;T&EupD_b{1nts&Mwc@)OSj~^=?IbOX&R1>eYXBmC{ zL9@2fA3wfcuz$0=yPG*>W3ft29@V+&)NWcRX<=cZ#FnZ=&t}w&nK+0BpI{_~N=Qf$ zk&yDS(M7%v&|#9K>TAE_0mh9}tQVQa{clJ8W+--5zGKb_e> z7i{?2vF}QQkJ;BDrK?K}CLXDS8aQfve*UpIrVGE}y+JP4YS%NznPMJHbDqC>YGkOP zcB7z}t(fUiG1THU;y1Bh7>HpuR5RgQ8bo#+L_2=M=_}re?YN0{7NcZ`UXD3Y5iCSi zE!rzyB~akuAqsbZ=YQ2=vIS9R&;z`xIrfgl28kURE$1V$9qB@xhU2g&GMKl}Z^h?d zst7yuxah`3-fwn(_`%{t;hZp68{=`LT236gjVWg;9UnyPP6~NSixi(Z&{P%w zmx1wyIesNuWj433$b4Qq?C~{OLu*;wmR$eMH>32TrZ6$N78NHlLsrtY>6rC#ha8etPe0?e0Q(ZKeHF=D79ol5!do=>NWYB|XD$ zmcxNmson9pzKD{fo~g3J^KYl*v74iHpJ*kpNAZKW9xma8b^Gf(w8Za|#&>l8`}ayI zjXx*=15X@3u=zq=y+DI0JSqy@vdoH#CmGk1@D&&SvTtDZ}VB zdTDjIV`f8OE)^7(w8P(ckvMOS1dUq{@hNRDH#Rj@SdEgi7`Kw`O_x~}JrYBw@%}r* z(R{JwS-+E?Qf}TK6V6Hd6zlI|6BN|R6dpTMxSdspEPOX;`A|ki2IKs2IZB5G9n`v) zbGF`3F;OW)slmD{ciq>U*>|l?G7&0aPpka~)VE(@cT^>1GjrlFzf+B$jCwybw_75`hGueD?;RXQXU-Ma zTe?bHw$eRifwk0&G&R4txih~x-#1_F2$H&x0ZYq$w-7F?tgI}?d3E=B?c}PjuTOT| zm7Dh|t)GeS*KFCq$Vl8@6B9Ihst){NtopUsFJt9?y`32!_wzhxz}Vm4hs0d*Z6Dbt zFVjFLZC;;z_7o_yEwagn?2B`v{aE))*prGFErT`9>>cES#yVqzJhSjB12pc7D8xbB2BjqUL zMxmE`kYla_^y8E_4$_{U0%W)tlB!)`RdZDaWrBh8Xd(AJ|JyEsClqMdu-t}nBcKe3Sgla0%0Vy9Jk=D~Z_Ej)zK?}eP5|?t0#swqaOz>iW zOgp!bVl&i#+||12uk}IoH>S;wy)(}Bzb^csr8XO}-+pc@Hu(PbWt?`R84QMReElmC z0^v!)@u&Q`cV=pCF8ODL8px&yH^E35a(7heoF^B{K0d#(a3O=KFRe6UYUx5vf7hz} zC~q=Y`m^-#Mny)hjmwaCA1pS(x1}iY8S3poxc}r25vBlL+jN` z;Cf##d|LJW*?-Yb#_*>93djv^$}hSk#E6Lba(O`Ly7Op;fFC zePqFK_u_@GeDl}-g#!yV*ZsNfaIQ_$sUMowHa7m?t8#RD@u?JQ5WRQ-21%5Gpo7$* z*{`C4&VgM~^Xmp32SuCt!s{P_MzZl|@!Cf1AIe(LgFF39UVR1UW-)9eP)K0$lg<(Q z__2mI>a&|05fCTqM=NQcmi)%813OoLHaKtnOIf}}Uw*j1m}ui}H6H|;6nIkDgyNeD z+a&oRd0Nn0uc-pRsW3>MA1o>;r3)tUSW1{V*w>{%D1jQ$DbYgq6+9i2Fn4qNjxR3D zLKu6ypu^HLhv<%25W>!uqaM=!%gf8>99&Gj&Z9yy5d7fI_BAyy=lk`)7&PaG&F1y! zF*7q`K}@S=F(JpB>^@1@xVQr^WnQX~18J=bZVi&Cyn+H2#0fM2#_5g69uyIul7}sk zeT(&KLQgL)lpDSbY@b}onwilciGpc?32{1Xxrk|PZCz`i!~!*3j)jos$St+`_W0om zBsV+T9|&hPRaG&_x3j(J*ea{hfgYi$(KkzO0T4>yhj-?~*jwGudXFTJAKI>`$}k~n z+S)RZZ)M*5A81J;2A)26|E=iF-@Y6Dd|t)8)Q>Ozb$X8u^ieZGBuWfiG6s3}N8V8t zpYN_2&2B)2rh&2>vCJ3&*l_%>6Ym+M%ckaO&bTZDKXEYr>?TdnJqC2y#6WSX?6?=2 z75ikI-UZHuYDJmTd%6k5J69;%;Vxhe5bm8^dCio+LrMR9{r>!mQHhqqXC6bN$?56! zAgIyLR6gfdyu6rcO2HDrxT{?u&xl(Jb7Z2FqJd#HtUvp$f097O{DyDoy7l4y=HJa7 zSO7%iB4%f2cVs|G zjF;a~d)W4XBzUzE3#d;)wHAO5$dFox<&j_61MOfGBS_z@U9>&i#dZBVY(BvSebxCu z_`B}VE%_?waOd`xpzzex)VLiqY>kbLsg$=cu(S6@eLkgGa2jL<{pD36{Yf5$uov&X zyx$yW@_mB)p8rrS!J~BAI1+STN6z=$3gP_(KEUv^XuTDQ(MDnHSN~B|6r1(jmzdWk z6P{Ro>?U}*c=$OOG!#M#!+IE1SRp*c1heq(T+L%(&?u6zPHh65oG$;p3+#NSb(>vSh=nV2XVKD^lcCnD2?PMwv4`YgEG-=orV>YZVU;RXtT+o{6LndUbWXoKY)vhq-o^3P+C|Er zR-fy~UncK<$wLQH?J^C<{0gUc;2QUgjg!Cf?lT>B-XGZWxCgvtER1V5Vz_oMY?MCJ z7&*42fVN~ZFfxj<5N7l^ze7u|(c33@VIneWvU5iKW-MdzRM+d!b2-4MtoKdZmEyw3 zX+t@+r~P&sL0-NagvqpRq+!gVZ- zE$(sCqOb)c;~Q%#6n~h184>e5YW2XeB^WK457Bhw1y;Bb z3Yb8uvfJWQy47dOf_~b@-tqfZL$^JNZJDbp-#BY|g)PZll$G)BPcj~U+SHg@TVoQ2 zq4|LSI<7~&Azb8HR1mlu>x}=N<`5{n>zR4g90mNHwf<`K}oHcq8DhypbRa;yqqxraPb30^k=S*%W*V$b?eM%lHwV{Y*fXtoh zbQbaarlPSSvM5@=V2UI*v=+upjRS5vcM2YPW{Pk-CtE|7jzi0$& z*f~}2VAlrEAApGoxN=TzZs3b}IGj1G`?8A{QtrLq8%1Gth{5|ycXUI<<>HAU78oWa zV)||(tK)(5jA033#o;Ci6whMx8J&ziC1#2b{V2GfJGp>e8D7isU3lBNmMa-`*M{iB zHFk~OFt&J>!Pp*ATjH@1Xw;Us?gr1?_W)2(cT(F($w>Zejh3qs9CDv}Ak42iVWoVL zYh@aaIJr#AN%XtMi2kmiR{?~294KjF3_sK#yeE9P@=$z%s-PjlCXAz${kgdZX3Y5F zA`XN_LC4wId5+G@+gqshUl6;Axp|->ep*_Zdc11Bx!f%J1ii>rKjYHP)ydZCnaAu! z4D-e}CGI<&=gr%r?U4+T zsMc0tS4AT;S_=cT05L$iUrAsBQDlrCjWf7%DBV~D0~l5mQ%5A6}m@D`u^!P zfUq+U?DRDQ8DT&f^+@r?EG#KVlr+2)rAxz}I!(%*!K)bzaYJ5(eJ5UW$%}_%x=V5C zHv*;bcfJlUO6dMF&TLLCL(ys)Twd_Fj|QMSDS*QOwX<(NlD5oL7hs5%{b|dy-ndtq zLOs1De8G2ncj%MBgc7=eJ~1Gu*m=A;sH&?Qvby?i#+C4gGrMfmd9-dR0pct_K1_pe3RUezcAe;t9SHDHc)<2-!0^T{eM#txD6C*5Yz zX>?%()`o+R536Z0a%4maIH`Ez9q}GnO8lR7ZFhELyfB1Z2IU?Q7@Zc6k9sX2rg+N^ zu>RYZ&sxt)99E-zZfQOw=38hwg|p~Z2KMU!ISmlK;PrM6z>8?k9Gsl~z2u(Qo)-sz zS9WjrF{EJsA~vxZlvI`EC2J+apy!-dGw_fr=vo0h2NWijjKfpOv(tyyuV1^Zh0y;C zptwbRhP=GI%Vnssq^CY9fHpu5+iKRPa+y&_{~S}P+|UHbJCZ^KH@3g4c1GymPZXBc zRHaB9QXYtGLPZwV32@a~h>8!Ql0*fg=O%CS6Pr0xhTlKCnBRBx+7ei9Uyl-rjEQ*~ z7azY0+5^r=x=8H~^F!t!{q!Q8Q3O8Qnl#_SZA8 zvJUs5d5?5;p?(PteiGLGZ?~unS@!pXbJLnXx(WzB(C>fKeu57= z!YP^`yAGT|Z>7cH*zez8K=T4T@!iRv;me7Pd(;-AsDS}_Ah!K`WkH$*diuYHQEyz= zChq~FtOsD>5JtgmEA10tvF|vO{{2aDFkSD70C>8ny%rharW{)x{+aP}yJ8)AWM?=S4q;vVFU|xrnV>{+t4|wmBbB zOrOr9!n(Rd1Ozt8`()h!Hx&kMqBwlCq;5$b*-v!jsaV_XCcAC}qn?t?!6-;TbPCg4 z^6CK0?n{3C0TTkS1z^RCTR`3E4m#YO*R|0>WGyV{3QJ30@~4#+Y`w41Q&U&(l=ry1 zzi1o1zI`F!iVw+cZB2(z>etz~7!ZchnltA~&I9?f6}QD|Q2*`HOIUY6KF@Qh!mJk) zV#=9>38}Om&#tCR*si+!ECOt&{&%A>PLpmg5TTILFl!a}>8*t%EPH@ZQHQS@s3k3O z`H@lW*5i0ai`;y~{1ylFB$5`2>s}9tL3r4Q(uOZCpFUY_`0pCiKBWR)pXTP`kRE^r zO14A=9)aB>w?1Hj#sQZEBU%3Rpu1-X#1N(xMBs{Ar3U?L-**I`>T&-B&05KgUArL-r9O^3q3Gn(xEF0rkx)nQR7 znu<|qumX;(yE_b8@#)aW>u%53s=*5GjA+hj0-x5xjBlx#_swebRQ2?dq0ppj8TbT1 zIFAw317Y=Q=lCIBpqKVuUh;+sB*eubGz*wPoKXL7EUeF?dI38HtRL)e*vU&Q2hpj* zwE5Q-!(-oqPjHuZdZQz3Ahs8far z>t*U!dfFDsoc9_MS@@(hgNf|DK&Hz8BTOrMtJa6HGiS>qQ6-g;5Jf54I1B(eAM8Tg_SKl^*RUGr&NKzHSt59Y9X^B`4xWLW! z5~eu1xkGy%=+d;fQ3y|`CJf8lCa(CuB+p3BNW~_4sBXz4%yyw(^~vbR-pCg*8wi3v zDtHo3oqh|zR6l5BT*oojO+F7d%R~B%=gt?4pJGuka1B(yaW7UiH8%EvDLdN&CQs~Q zilSW^SVjo*Cv7z0dOfu32sJ@Ts6!a zi2vuTSicSz*c97f-$`Uh;jz-&PkO^8V1IxiVlTkkpZl)s`*q8fj_+HHjK}`UY7>G) zqyFko3OufIfIwM!@$udL&Efh=djKfXEYTWN2pS7?$AZ_%FrZOG+d|4)rcO>zLBa$^ zMFJ>3(6a&K*J4AB2XL)p>m;kSm7J8vR#ZZwGj9K5gG)|P(PN+GCTNjXsks1g9Av5` zJ)IojzqX$s4*7RA>XZH)7C-L{w~LI7G`TpK1_5Y=@cA;-4ue82GLZ)!Z@0RekQw!W4oCXmk7cLka@x=KGm$cE_; zflnkkKODP!%?2@FP(?+B!i~j=kG|psM`$)?ST{0&BuXFTf~NO^de?#5V7m;ouwjx3 z?u@>;w@79Odnc+_CQ}Oy0CN1f3M-FIQ(HZ`0)dbDsy{yB*mJmo6zx;L!Of1oK1^mG zEo`V|Rnn$=KgQ=a3cw=$DG9tx!_z@JjL3_$QyGQ4GLj}hj|Z(_=rD~516Yi3+Q2yiM1^3x#oXLLP}GQX771Hl|H2^Ib}^l?J^mmj|o{2YY9Y)rC;dj=@GFckLu3Zb#`rE5Y4;5 z?pH4SFA%Y2g$}JF5c(0dwYAv|vb|xQtG@Z51cN#qJ4^>}pa$4$ZhjuDour!e9jrQa zxKdVeHTLjcvMyb`>E>5SYF?)eOr;Frh=hcMaC7xp#HO98si3{mrbvqkCTUy`hE7mj zCJWU;11=~o9vVPH5(DTNSb2oPo~;M{0q|)lPZ@#VrMfAxVPGc=A`1W*SyE9k!q(vQ z>}++TKNcJfD3NXh#P|H%6_g&Z^#KX=Izl$kny>B`f?Wi>AhY-a_GW+*$wm(aUKcK)WVZ7w z4-1QTA7Hl#04trNs>KF~Ke(Gf7;y397aD+k z9U!|wel4|zg3{8F0dOh?bPNnfprEzG`I=mO3=j4kbui~ z9I%xDY{7yxcmTPQ5|6N7fPUswR2+qdzIB2*g8UB1ISoxsi%k&fO5~}5lZnHp#IOq3 zFcvHxEyC+B)Dles@xe6DzjbP^%e6DG*Va3)ne-y<9*CY(5*}2|YoL7EAp7Tw><|$Y zypfy4y875TYux7e(JVs_6pK(GegN%tXr)uu_{Qra1_5bW7?MRpAN||tAA`pSb+U3Z z>|66HKRy7;uUB%;8KUc|Hnyj>SKN;#U$2*@kM!Qxxb(;G)B7C**=X$$O`H$<$WRUR zRkbCIhK6s)m7EknF^RLLS*0nxq06O&Lj5z7O|2~r*tvh5iW9TNFuTinWNAe{2E50) zSWVxR4(cRNo$E2?G=De96&j~2wc}rK)t8lvebx6Pkmahn#QcAE%y5wQ^xIB7rlVb> z#}bMB#yc_krHdmp--#eFX<2@-F4y`0*@y(l4Pv*@e*)_N=>h+}N$Ec?{m*OvX%GM3 zm;RqK_6GNgZpDwNs^~-#OAB*i`e%L}0=s39XG7svsdRTQO)M>*P?nVGE;ANDDW;Az zMB%b=V(x4NTw{UwV22Uy8kXF2kC~%X3~YirORX%TcipM-=RGv6TSv@Lu34_WRt43DjhDlQU zAFF2uxN0^vHq1>BH)Z%`Lu8f)TLFe$-8_3d-(V@IxKw#7 zWt7ZNU('healthy'); + const [warnings, setWarnings] = useState([]); + const [resourceGroups, setResourceGroups] = useState([]); + const [instanceMetrics, setInstanceMetrics] = useState(null); + + useEffect(() => { + async function getData() { + const health: HealthStatus = await fetchHealthStatus(); + setHealthStatus(health); + + const warningsData: Warning[] = await fetchWarnings(); + setWarnings(warningsData); + + const resourceGroupsData: ResourceGroupDetails[] = await fetchResourceGroupDetails(); + setResourceGroups(resourceGroupsData); + + const instanceData: InstanceMetrics = await fetchInstanceMetrics(); + setInstanceMetrics(instanceData); + } + void getData(); + }, []); + + return ( + + + + + +

Overall Health

+ + {healthStatus} + +
    + {warnings.map((warning, index) => ( +
  • + {warning.message}{' '} + + {warning.resourceGroup} + +
  • + ))} +
+
+
+ + + + +

Resource Groups

+
+ {resourceGroups.map((group) => ( + + + + {group.name} + +
+ + {group.healthStatus} + +

Type: {group.type}

+

Component Count: {group.componentCount}

+
+
+
+ ))} +
+
+
+ +
+ + + + +

Cluster Info

+ {instanceMetrics && ( + + +

Zookeeper Servers

+ + {instanceMetrics.zooKeepers.map((zk, index) => ( + {zk} + ))} + + + +

Volumes

+ + {instanceMetrics.volumes.map((volume, index) => ( + {volume} + ))} + + +
+ )} +
+
+ +
+
+ ); +} + +export default HomePage; \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupPage.tsx b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupPage.tsx new file mode 100644 index 00000000000..e0f5eb3d246 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupPage.tsx @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import { useState, useEffect } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { + fetchAllMetrics, + fetchCompactorsSummary, + fetchScanServerSummary, + fetchTabletServerSummary, +} from '../api'; +import { ServerData, ServerTypeDisplayName, SummaryMetrics } from '../types'; +import { Table } from 'react-bootstrap'; + +function ResourceGroupPage() { + const { rgName } = useParams<{ rgName: string }>(); // grab the resource group name from the URL + const [servers, setServers] = useState([]); + const [compactorMetrics, setCompactorMetrics] = useState({}); + const [sserverMetrics, setSserverMetrics] = useState({}); + const [tserverMetrics, setTserverMetrics] = useState({}); + + useEffect(() => { + async function getData() { + const allMetrics: ServerData[] = await fetchAllMetrics(); + const filteredServers: ServerData[] = allMetrics.filter((s) => s.resourceGroup === rgName); + setServers(filteredServers); + + const [compactorData, sserverData, tserverData] = await Promise.all([ + fetchCompactorsSummary(rgName), + fetchScanServerSummary(rgName), + fetchTabletServerSummary(rgName), + ]); + + setCompactorMetrics(compactorData); + setSserverMetrics(sserverData); + setTserverMetrics(tserverData); + } + void getData(); + }, [rgName]); + + return ( +
+

Resource Group: {rgName}

+

Components in this Resource Group:

+ {servers.length === 0 ? ( +

No components found in this resource group.

+ ) : ( + + + + + + + + + {servers.map((server) => ( + + + + + ))} + +
HostServer Type
+ {server.host} + {ServerTypeDisplayName.get(server.serverType)}
+ )} + +

Compactor Metrics

+ {Object.keys(compactorMetrics).length === 0 ? ( +

No compactor metrics available for this resource group.

+ ) : ( + + + + + + + + + + + {Object.entries(compactorMetrics).map(([metricName, stats]) => ( + + + + + + + ))} + +
Metric NameCountMeanMax
{metricName}{stats.count}{stats.mean}{stats.max}
+ )} + +

Scan Server Metrics

+ {Object.keys(sserverMetrics).length === 0 ? ( +

No scan server metrics available for this resource group.

+ ) : ( + + + + + + + + + + + {Object.entries(sserverMetrics).map(([metricName, stats]) => ( + + + + + + + ))} + +
Metric NameCountMeanMax
{metricName}{stats.count}{stats.mean}{stats.max}
+ )} + +

Tablet Server Metrics

+ {Object.keys(tserverMetrics).length === 0 ? ( +

No tablet server metrics available for this resource group.

+ ) : ( + + + + + + + + + + + {Object.entries(tserverMetrics).map(([metricName, stats]) => ( + + + + + + + ))} + +
Metric NameCountMeanMax
{metricName}{stats.count}{stats.mean}{stats.max}
+ )} +
+ ); +} + +export default ResourceGroupPage; diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupsOverviewPage.tsx b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupsOverviewPage.tsx new file mode 100644 index 00000000000..28883e199d2 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ResourceGroupsOverviewPage.tsx @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import { useState, useEffect } from 'react'; +import { Card, Row, Col, Container } from 'react-bootstrap'; +import { Link } from 'react-router-dom'; +import { fetchDeploymentMetrics } from '../api'; +import { DeploymentsMetrics, ServerType, ServerTypeDisplayName } from '../types'; + +function ResourceGroupsOverviewPage() { + const [deploymentMetrics, setDeploymentMetrics] = useState({}); + + useEffect(() => { + async function getDeploymentMetrics() { + try { + const data = await fetchDeploymentMetrics(); + setDeploymentMetrics(data); + } catch (error) { + console.error('Error fetching deployment metrics:', error); + } + } + void getDeploymentMetrics(); + }, []); + + return ( + +

Resource Groups Deployment Metrics

+ + {Object.entries(deploymentMetrics).map(([resourceGroup, serverTypes]) => { + const hasUnresponsiveHosts = Object.values(serverTypes).some( + (metrics) => metrics.notRespondedHosts.length > 0 + ); + + return ( + + + + + {resourceGroup} + +
+ + {Object.entries(serverTypes).map(([serverType, metrics]) => { + const hasUnresponsiveHosts = metrics.notRespondedHosts.length > 0; + + return ( + + + + + {ServerTypeDisplayName.get(serverType as ServerType)} + +
    +
  • + Configured: {metrics.configured} +
  • +
  • + Responded: {metrics.responded} +
  • + {hasUnresponsiveHosts ? ( + <> + Unresponsive Hosts: +
      + {metrics.notRespondedHosts.map((host) => ( +
    • + {host} +
    • + ))} +
    + + ) : ( +
  • Unresponsive: 0
  • + )} +
+
+
+ + ); + })} +
+
+
+
+ + ); + })} +
+
+ ); +} + +export default ResourceGroupsOverviewPage; \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerOverviewPage.tsx b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerOverviewPage.tsx new file mode 100644 index 00000000000..ac85437adcb --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerOverviewPage.tsx @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import { useState, useEffect } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { fetchAllMetrics } from '../api'; +import { ServerData, ServerType, ServerTypeDisplayName } from '../types'; +import { Table } from 'react-bootstrap'; + +function ServerOverviewPage() { + const { serverType } = useParams<{ serverType?: string }>(); + const [servers, setServers] = useState([]); + + useEffect(() => { + async function getServers() { + const data = await fetchAllMetrics(); + const filteredServers = serverType + ? data.filter((s) => s.serverType === serverType.toUpperCase() as ServerType) + : data; + setServers(filteredServers); + } + void getServers(); + }, [serverType]); + + return ( +
+

{ServerTypeDisplayName.get(serverType as ServerType)} Overview

+

+ {servers.length === 0 ? ( +

No servers available.

+ ) : ( + + + + + + + + + + {servers.map((server) => ( + + + + + + ))} + +
HostTypeResource Group
+ {server.host} + {ServerTypeDisplayName.get(server.serverType)} + + {server.resourceGroup} + +
+ )} +
+ ); +} + +export default ServerOverviewPage; diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerPage.tsx b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerPage.tsx new file mode 100644 index 00000000000..2894bf40c84 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/ServerPage.tsx @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import { useState, useEffect } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { fetchAllMetrics, fetchManagerMetrics, fetchGCMetrics } from '../api'; +import { ServerData, ServerTypeDisplayName } from '../types'; +import { Table } from 'react-bootstrap'; + +function ServerPage() { + const { serverId } = useParams<{ serverId: string }>(); + const [serverData, setServerData] = useState(null); + + useEffect(() => { + async function getServerData() { + let data: ServerData | null = null; + if (serverId === 'MANAGER') { + data = await fetchManagerMetrics(); + } else if (serverId === 'GARBAGE_COLLECTOR') { + data = await fetchGCMetrics(); + } else { + const allData = await fetchAllMetrics(); + data = allData.find((s) => s.host === serverId) ?? null; + } + setServerData(data); + } + void getServerData(); + }, [serverId]); + + if (!serverData) { + return
No data found...
; + } + + return ( +
+

{ServerTypeDisplayName.get(serverData.serverType)}

+

Hostname: {serverData.host}

+

+ Resource Group:{' '} + + {serverData.resourceGroup} + +

+

+

Metrics:

+ + + + + + + + + + {serverData.metrics.map((metric, index) => ( + + + + + + ))} + +
NameValueType
{metric.name}{metric.value}{metric.type}
+
+ ); +} + +export default ServerPage; diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/TablesPage.tsx b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/TablesPage.tsx new file mode 100644 index 00000000000..f28dcfe4b8e --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/components/TablesPage.tsx @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import { useState, useEffect } from 'react'; +import { fetchTablesMetrics } from '../api'; +import { TablesMetrics } from '../types'; +import { Table } from 'react-bootstrap'; + +function TablesPage() { + const [tableData, setTableData] = useState({}); + + useEffect(() => { + async function getData() { + try { + const data: TablesMetrics = await fetchTablesMetrics(); + setTableData(data); + } catch (error) { + console.error('Error fetching table metrics:', error); + } + } + void getData(); + }, []); + + return ( + <> +

Tables

+ {Object.keys(tableData).length === 0 ? ( +

No table data available.

+ ) : ( + + + + + + + + + + + + + {Object.entries(tableData).map(([tableName, metrics]) => ( + + + + + + + + + ))} + +
Table NameTotal EntriesTotal Size On DiskTotal FilesTotal WALsTotal Tablets
{tableName}{metrics.totalEntries}{metrics.totalSizeOnDisk}{metrics.totalFiles}{metrics.totalWals}{metrics.totalTablets}
+ )} + + ); +} + +export default TablesPage; \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/index.css b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/index.css new file mode 100644 index 00000000000..d3d4106380a --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/index.css @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +:root { + --bs-body-font-family: verdana, arial, sans-serif; +} + +#main { + padding: 0.6em; + /*overflow: scroll;*/ + width: 90%; + text-align: center; + margin: 0 auto; +} + +h1, h2, h3, h4, h5, h6 { + text-align: center; +} + +#accumulo-avatar { + margin: -5px 10px 0 0; + border-radius: 5px; + width: 32px; + height: 32px; +} + +.resource-groups-scroll { + max-height: 400px; + overflow-y: auto; +} + +.homepage-container { + margin: 20px auto; + max-width: 1200px; + padding: 20px; +} + +.section-card { + margin-bottom: 20px; +} + +.table { + width: auto; +} \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/main.tsx b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/main.tsx new file mode 100644 index 00000000000..f219a09d4c2 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/main.tsx @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.tsx'; +import { BrowserRouter } from 'react-router-dom'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import './index.css'; // load custom styles after bootstrap so they can override + +createRoot(document.getElementById('root')!).render( + + + + + +); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/types.ts b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/types.ts new file mode 100644 index 00000000000..0a38b1077bc --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/types.ts @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ + +export enum ServerType { + GARBAGE_COLLECTOR = 'GARBAGE_COLLECTOR', + TABLET_SERVER = 'TABLET_SERVER', + SCAN_SERVER = 'SCAN_SERVER', + MANAGER = 'MANAGER', + COMPACTOR = 'COMPACTOR', +} + +// Map of ServerType to how we want to display the string in the UI +export const ServerTypeDisplayName = new Map([ + [ServerType.GARBAGE_COLLECTOR, 'Garbage Collector'], + [ServerType.TABLET_SERVER, 'Tablet Server'], + [ServerType.SCAN_SERVER, 'Scan Server'], + [ServerType.MANAGER, 'Manager'], + [ServerType.COMPACTOR, 'Compactor'], +]); + +export interface Tag { + key: string; + value: string; +} + +export interface Metric { + name: string; + type: string; + tags: Tag[]; + value: number; +} + +export interface ServerData { + timestamp: number; + serverType: ServerType; + resourceGroup: string; + host: string; + metrics: Metric[]; +} + +export type SummaryMetrics = Record; + +export interface InstanceMetrics { + instanceName: string; + instanceUUID: string; + zooKeepers: string[]; + volumes: string[]; +} + +export interface CompactionsMetrics { + groupName: string; + compactor: string; + updates: Record; + job: { + jobId: string; + status: string; + }; +} + +export interface ProblemMetrics { + host: string; + port: number; +} + +export type HealthStatus = 'healthy' | 'degraded' | 'unhealthy'; + +export interface Warning { + resourceGroup: string; + message: string; +} + +export interface ResourceGroupDetails { + name: string; + healthStatus: HealthStatus; + type: string; + componentCount: number; +} + +interface TableMetrics { + totalEntries: number; + totalSizeOnDisk: number; + totalFiles: number; + totalWals: number; + totalTablets: number; + availableAlways: number; + availableOnDemand: number; + availableNever: number; + totalAssignedTablets: number; + totalAssignedToDeadServerTablets: number; + totalHostedTablets: number; + totalSuspendedTablets: number; + totalUnassignedTablets: number; +} + +export type TablesMetrics = Record; + +interface ServerMetrics { + configured: number; + responded: number; + notResponded: number; + notRespondedHosts: string[]; +} + +export type DeploymentsMetrics = Record>; \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/vite-env.d.ts b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/vite-env.d.ts new file mode 100644 index 00000000000..bd74b6790e8 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/src/vite-env.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ + +/// diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.json b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.json new file mode 100644 index 00000000000..5a2def4b7a3 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.tsbuildinfo b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.tsbuildinfo new file mode 100644 index 00000000000..febe97c917c --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.app.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/App.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/HomePage.tsx","./src/components/ResourceGroupPage.tsx","./src/components/ResourceGroupsOverviewPage.tsx","./src/components/ServerOverviewPage.tsx","./src/components/ServerPage.tsx","./src/components/TablesPage.tsx"],"version":"5.6.3"} \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.json b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.json new file mode 100644 index 00000000000..1ffef600d95 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.json b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.json new file mode 100644 index 00000000000..9dad70185e2 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.tsbuildinfo b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.tsbuildinfo new file mode 100644 index 00000000000..75ea0011dff --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./vite.config.ts"],"version":"5.6.3"} \ No newline at end of file diff --git a/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/vite.config.ts b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/vite.config.ts new file mode 100644 index 00000000000..58dc4bf3742 --- /dev/null +++ b/server/monitor/src/main/resources/org/apache/accumulo/newmonitor/vite.config.ts @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ + +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + '/metrics/groups': { + target: 'http://localhost:43331', + changeOrigin: true, + }, + '/metrics': { + target: 'http://localhost:43331', + changeOrigin: true, + }, + }, + }, +}) From 11978f143c429e6c3bbcc4510ba148c3a75ba0d0 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Tue, 12 Nov 2024 21:01:45 +0000 Subject: [PATCH 19/22] Prevent NPEs --- .../monitor/next/InformationFetcher.java | 11 +++-- .../serializers/MetricResponseSerializer.java | 44 ++++++++++--------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index 544393d26fb..74a07705de7 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -332,9 +332,14 @@ public void run() { "All: {}, Manager: {}, Garbage Collector: {}, Compactors: {}, Scan Servers: {}, Tablet Servers: {}", allMetrics.estimatedSize(), summary.getManager() != null, summary.getGarbageCollector() != null, - summary.getCompactorAllMetricSummary().entrySet().iterator().next().getValue().count(), - summary.getSServerAllMetricSummary().entrySet().iterator().next().getValue().count(), - summary.getTServerAllMetricSummary().entrySet().iterator().next().getValue().count()); + summary.getCompactorAllMetricSummary().isEmpty() ? 0 + : summary.getCompactorAllMetricSummary().entrySet().iterator().next().getValue() + .count(), + summary.getSServerAllMetricSummary().isEmpty() ? 0 + : summary.getSServerAllMetricSummary().entrySet().iterator().next().getValue() + .count(), + summary.getTServerAllMetricSummary().isEmpty() ? 0 : summary.getTServerAllMetricSummary() + .entrySet().iterator().next().getValue().count()); SystemInformation oldSummary = summaryRef.getAndSet(summary); if (oldSummary != null) { diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java index f1db0665f21..1bddaa6b52d 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/MetricResponseSerializer.java @@ -40,33 +40,35 @@ public void serialize(MetricResponse value, JsonGenerator gen, SerializerProvide gen.writeStringField("resourceGroup", value.getResourceGroup()); gen.writeStringField("host", value.getServer()); gen.writeArrayFieldStart("metrics"); - for (final ByteBuffer binary : value.getMetrics()) { - FMetric fm = FMetric.getRootAsFMetric(binary); - gen.writeStartObject(); - gen.writeStringField("name", fm.name()); - gen.writeStringField("type", fm.type()); - gen.writeArrayFieldStart("tags"); - for (int i = 0; i < fm.tagsLength(); i++) { - FTag t = fm.tags(i); + if (value.getMetrics() != null) { + for (final ByteBuffer binary : value.getMetrics()) { + FMetric fm = FMetric.getRootAsFMetric(binary); gen.writeStartObject(); - gen.writeStringField(t.key(), t.value()); + gen.writeStringField("name", fm.name()); + gen.writeStringField("type", fm.type()); + gen.writeArrayFieldStart("tags"); + for (int i = 0; i < fm.tagsLength(); i++) { + FTag t = fm.tags(i); + gen.writeStartObject(); + gen.writeStringField(t.key(), t.value()); + gen.writeEndObject(); + } + gen.writeEndArray(); + // Write the non-zero number as the value + if (fm.lvalue() > 0) { + gen.writeNumberField("value", fm.lvalue()); + } else if (fm.ivalue() > 0) { + gen.writeNumberField("value", fm.ivalue()); + } else if (fm.dvalue() > 0.0d) { + gen.writeNumberField("value", fm.dvalue()); + } else { + gen.writeNumberField("value", 0); + } gen.writeEndObject(); } gen.writeEndArray(); - // Write the non-zero number as the value - if (fm.lvalue() > 0) { - gen.writeNumberField("value", fm.lvalue()); - } else if (fm.ivalue() > 0) { - gen.writeNumberField("value", fm.ivalue()); - } else if (fm.dvalue() > 0.0d) { - gen.writeNumberField("value", fm.dvalue()); - } else { - gen.writeNumberField("value", 0); - } gen.writeEndObject(); } - gen.writeEndArray(); - gen.writeEndObject(); } } From 0c8fc3d5e6373162f758907e5bca4d4fd2914490 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Wed, 13 Nov 2024 20:08:54 +0000 Subject: [PATCH 20/22] Only process meters from one registry, removes duplicate meters --- .../server/metrics/MetricServiceHandler.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java index 1c28d1d96ef..0bbccf86787 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java @@ -18,9 +18,6 @@ */ package org.apache.accumulo.server.metrics; -import java.util.ArrayList; -import java.util.List; - import org.apache.accumulo.core.clientImpl.thrift.SecurityErrorCode; import org.apache.accumulo.core.clientImpl.thrift.TInfo; import org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException; @@ -36,7 +33,6 @@ import com.google.common.net.HostAndPort; import com.google.flatbuffers.FlatBufferBuilder; -import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; public class MetricServiceHandler implements MetricService.Iface { @@ -86,17 +82,12 @@ public MetricResponse getMetrics(TInfo tinfo, TCredentials credentials) return response; } - List registries = new ArrayList<>(Metrics.globalRegistry.getRegistries()); - registries.add(Metrics.globalRegistry); - registries.forEach(r -> { - r.getMeters().forEach(m -> { - if (m.getId().getName().startsWith("accumulo.")) { - m.match(response::writeMeter, response::writeMeter, response::writeTimer, - response::writeDistributionSummary, response::writeLongTaskTimer, - response::writeMeter, response::writeMeter, response::writeFunctionTimer, - response::writeMeter); - } - }); + Metrics.globalRegistry.getMeters().forEach(m -> { + if (m.getId().getName().startsWith("accumulo.")) { + m.match(response::writeMeter, response::writeMeter, response::writeTimer, + response::writeDistributionSummary, response::writeLongTaskTimer, response::writeMeter, + response::writeMeter, response::writeFunctionTimer, response::writeMeter); + } }); builder.clear(); From da8a4888a5717cc0a24309bf7d021d4514321307 Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Wed, 13 Nov 2024 22:41:51 +0000 Subject: [PATCH 21/22] Fix metric aggregation Metric aggregation for the metrics summary endpoints over-aggregated metrics with multiple statistics down to one instance. For example, on a local server with two Tablet Servers running you would get the following metrics: Endpoint: /metrics/tservers/detail/default ``` // first tserver {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"count"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"avg"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"max"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"sum"}],"value":0} {"name":"accumulo.blockcache.index.requestcount","type":"COUNTER","tags":[{"statistic":"count"}],"value":58.0}, // second tserver {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"count"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"avg"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"max"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"sum"}],"value":0}, {"name":"accumulo.blockcache.index.requestcount","type":"COUNTER","tags":[{"statistic":"count"}],"value":10.0} ``` Endpoint: /metrics/tservers/summary/default ``` "accumulo.tserver.updates.commit.prep":{"count":"8","mean":"0.0","max":"0.0"} "accumulo.blockcache.index.requestcount":{"count":"2","mean":"34.0","max":"58.0"} ``` Endpoint: /metrics/tservers/summary ``` "accumulo.tserver.updates.commit.prep":{"count":"8","mean":"0.0","max":"0.0"} "accumulo.blockcache.index.requestcount":{"count":"2","mean":"34.0","max":"58.0"} ``` As you can see the `accumulo.tserver.updates.commit.prep` metric was collapsed one metric in the summary view and the statistic (count, avg, sum, and max) values were lost. This commit fixes the issue by placing the statistic name at the end of the metric name. Below is a sample of the fixed metrics: Endpoint: /metrics/tservers/detail/default ``` //first tserver {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"count"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"avg"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"max"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"sum"}],"value":0}, {"name":"accumulo.blockcache.index.requestcount","type":"COUNTER","tags":[{"statistic":"count"}],"value":0}, //second tserver {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"count"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"avg"}],"value":0}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"max"}],"value":0.013}, {"name":"accumulo.tserver.updates.commit.prep","type":"TIMER","tags":[{"statistic":"sum"}],"value":0}, {"name":"accumulo.blockcache.index.requestcount","type":"COUNTER","tags":[{"statistic":"count"}],"value":33.0}, ``` Endpoint: /metrics/tservers/summary/default ``` "accumulo.tserver.updates.commit.prep.avg":{"count":"2","mean":"0.0","max":"0.0"}, "accumulo.tserver.updates.commit.prep.sum":{"count":"2","mean":"0.0","max":"0.0"}, "accumulo.tserver.updates.commit.prep.max":{"count":"2","mean":"0.0065","max":"0.013"}, "accumulo.tserver.updates.commit.prep.count":{"count":"2","mean":"3.5","max":"7.0"} "accumulo.blockcache.index.requestcount.count":{"count":"2","mean":"31.0","max":"62.0"} ``` Endpoint: /metrics/tservers/summary ``` "accumulo.tserver.updates.commit.prep.avg":{"count":"2","mean":"0.0","max":"0.0"}, "accumulo.tserver.updates.commit.prep.sum":{"count":"2","mean":"0.0","max":"0.0"}, "accumulo.tserver.updates.commit.prep.max":{"count":"2","mean":"0.0","max":"0.0"}, "accumulo.tserver.updates.commit.prep.count":{"count":"2","mean":"0.0","max":"0.0"}, "accumulo.blockcache.index.requestcount.count":{"count":"2","mean":"31.0","max":"62.0"} ``` --- .../server/metrics/MetricResponseWrapper.java | 33 ++++++------ .../server/metrics/MetricServiceHandler.java | 20 ++++--- .../monitor/next/InformationFetcher.java | 19 +++---- .../accumulo/monitor/next/NewMonitor.java | 3 ++ .../monitor/next/SystemInformation.java | 53 +++++++++++-------- .../next/serializers/IdSerializer.java | 51 ++++++++++++++++++ 6 files changed, 122 insertions(+), 57 deletions(-) create mode 100644 server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/IdSerializer.java diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java index ff60e1a4fce..eae88530e99 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricResponseWrapper.java @@ -67,8 +67,9 @@ private void reset(int nameRef, int typeRef, int tagsRef) { } private static final long serialVersionUID = 1L; + private static final TimeUnit UNIT = TimeUnit.SECONDS; + public static final String STATISTIC_TAG = "statistic"; - private final TimeUnit UNIT = TimeUnit.SECONDS; private transient final FlatBufferBuilder builder; private transient final CommonRefs common = new CommonRefs(); @@ -169,25 +170,25 @@ public Consumer writeMeter(Meter meter) { } public Consumer writeFunctionTimer(FunctionTimer ft) { - addMetric(ft.getId(), List.of(Tag.of("statistic", "count")), ft.count()); - addMetric(ft.getId(), List.of(Tag.of("statistic", "average")), ft.mean(UNIT)); - addMetric(ft.getId(), List.of(Tag.of("statistic", "sum")), ft.totalTime(UNIT)); + addMetric(ft.getId(), List.of(Tag.of(STATISTIC_TAG, "count")), ft.count()); + addMetric(ft.getId(), List.of(Tag.of(STATISTIC_TAG, "average")), ft.mean(UNIT)); + addMetric(ft.getId(), List.of(Tag.of(STATISTIC_TAG, "sum")), ft.totalTime(UNIT)); return null; } public Consumer writeTimer(Timer t) { - addMetric(t.getId(), List.of(Tag.of("statistic", "count")), t.count()); - addMetric(t.getId(), List.of(Tag.of("statistic", "avg")), t.mean(UNIT)); - addMetric(t.getId(), List.of(Tag.of("statistic", "max")), t.max(UNIT)); - addMetric(t.getId(), List.of(Tag.of("statistic", "sum")), t.totalTime(UNIT)); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "count")), t.count()); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "avg")), t.mean(UNIT)); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "max")), t.max(UNIT)); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "sum")), t.totalTime(UNIT)); return null; } public Consumer writeLongTaskTimer(LongTaskTimer t) { - addMetric(t.getId(), List.of(Tag.of("statistic", "avg")), t.mean(UNIT)); - addMetric(t.getId(), List.of(Tag.of("statistic", "max")), t.max(UNIT)); - addMetric(t.getId(), List.of(Tag.of("statistic", "duration")), t.duration(UNIT)); - addMetric(t.getId(), List.of(Tag.of("statistic", "active")), t.activeTasks()); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "avg")), t.mean(UNIT)); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "max")), t.max(UNIT)); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "duration")), t.duration(UNIT)); + addMetric(t.getId(), List.of(Tag.of(STATISTIC_TAG, "active")), t.activeTasks()); return null; } @@ -198,10 +199,10 @@ public Consumer writeDistributionSummary(DistributionSummar addMetric(d.getId(), List.of(Tag.of("percentile", Double.toString(p.percentile()))), p.value()); } - addMetric(d.getId(), List.of(Tag.of("statistic", "count")), d.count()); - addMetric(d.getId(), List.of(Tag.of("statistic", "avg")), d.mean()); - addMetric(d.getId(), List.of(Tag.of("statistic", "max")), d.max()); - addMetric(d.getId(), List.of(Tag.of("statistic", "sum")), d.totalAmount()); + addMetric(d.getId(), List.of(Tag.of(STATISTIC_TAG, "count")), d.count()); + addMetric(d.getId(), List.of(Tag.of(STATISTIC_TAG, "avg")), d.mean()); + addMetric(d.getId(), List.of(Tag.of(STATISTIC_TAG, "max")), d.max()); + addMetric(d.getId(), List.of(Tag.of(STATISTIC_TAG, "sum")), d.totalAmount()); return null; } diff --git a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java index 0bbccf86787..69dd635035c 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java +++ b/server/base/src/main/java/org/apache/accumulo/server/metrics/MetricServiceHandler.java @@ -78,18 +78,16 @@ public MetricResponse getMetrics(TInfo tinfo, TCredentials credentials) response.setResourceGroup(resourceGroup); response.setTimestamp(System.currentTimeMillis()); - if (!ctx.getMetricsInfo().isMetricsEnabled()) { - return response; + if (ctx.getMetricsInfo().isMetricsEnabled()) { + Metrics.globalRegistry.getMeters().forEach(m -> { + if (m.getId().getName().startsWith("accumulo.")) { + m.match(response::writeMeter, response::writeMeter, response::writeTimer, + response::writeDistributionSummary, response::writeLongTaskTimer, + response::writeMeter, response::writeMeter, response::writeFunctionTimer, + response::writeMeter); + } + }); } - - Metrics.globalRegistry.getMeters().forEach(m -> { - if (m.getId().getName().startsWith("accumulo.")) { - m.match(response::writeMeter, response::writeMeter, response::writeTimer, - response::writeDistributionSummary, response::writeLongTaskTimer, response::writeMeter, - response::writeMeter, response::writeFunctionTimer, response::writeMeter); - } - }); - builder.clear(); return response; } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index 74a07705de7..a1285146284 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -73,6 +73,7 @@ import com.github.benmanes.caffeine.cache.Scheduler; import com.google.common.net.HostAndPort; +import io.micrometer.core.instrument.Meter.Id; import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; public class InformationFetcher implements RemovalListener, Runnable { @@ -408,10 +409,10 @@ public Collection getCompactors(String resourceGroup) { return allMetrics.getAllPresent(servers).values(); } - public Map + public Map getCompactorResourceGroupMetricSummary(String resourceGroup) { validateResourceGroup(resourceGroup); - final Map metrics = + final Map metrics = getSummary().getCompactorResourceGroupMetricSummary(resourceGroup); if (metrics == null) { return Map.of(); @@ -419,7 +420,7 @@ public Collection getCompactors(String resourceGroup) { return metrics; } - public Map getCompactorAllMetricSummary() { + public Map getCompactorAllMetricSummary() { return getSummary().getCompactorAllMetricSummary(); } @@ -432,10 +433,10 @@ public Collection getScanServers(String resourceGroup) { return allMetrics.getAllPresent(servers).values(); } - public Map + public Map getScanServerResourceGroupMetricSummary(String resourceGroup) { validateResourceGroup(resourceGroup); - final Map metrics = + final Map metrics = getSummary().getSServerResourceGroupMetricSummary(resourceGroup); if (metrics == null) { return Map.of(); @@ -443,7 +444,7 @@ public Collection getScanServers(String resourceGroup) { return metrics; } - public Map getScanServerAllMetricSummary() { + public Map getScanServerAllMetricSummary() { return getSummary().getSServerAllMetricSummary(); } @@ -456,10 +457,10 @@ public Collection getTabletServers(String resourceGroup) { return allMetrics.getAllPresent(servers).values(); } - public Map + public Map getTabletServerResourceGroupMetricSummary(String resourceGroup) { validateResourceGroup(resourceGroup); - final Map metrics = + final Map metrics = getSummary().getTServerResourceGroupMetricSummary(resourceGroup); if (metrics == null) { return Map.of(); @@ -467,7 +468,7 @@ public Collection getTabletServers(String resourceGroup) { return metrics; } - public Map getTabletServerAllMetricSummary() { + public Map getTabletServerAllMetricSummary() { return getSummary().getTServerAllMetricSummary(); } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java index 13aeab0e331..e8b808bd79b 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/NewMonitor.java @@ -33,6 +33,7 @@ import org.apache.accumulo.core.tabletserver.thrift.TExternalCompactionJob; import org.apache.accumulo.core.util.threads.Threads; import org.apache.accumulo.monitor.next.serializers.CumulativeDistributionSummarySerializer; +import org.apache.accumulo.monitor.next.serializers.IdSerializer; import org.apache.accumulo.monitor.next.serializers.MetricResponseSerializer; import org.apache.accumulo.monitor.next.serializers.TabletIdSerializer; import org.apache.accumulo.monitor.next.serializers.ThriftSerializer; @@ -54,6 +55,7 @@ import io.javalin.http.staticfiles.Location; import io.javalin.json.JavalinJackson; import io.javalin.security.RouteRole; +import io.micrometer.core.instrument.Meter.Id; import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; public class NewMonitor implements Connection.Listener { @@ -107,6 +109,7 @@ public void start() throws IOException { config.bundledPlugins.enableRouteOverview("/routes", new RouteRole[] {}); config.jsonMapper(new JavalinJackson().updateMapper(mapper -> { SimpleModule module = new SimpleModule(); + module.addKeySerializer(Id.class, new IdSerializer()); module.addSerializer(MetricResponse.class, new MetricResponseSerializer()); module.addSerializer(TExternalCompaction.class, new ThriftSerializer()); module.addSerializer(TExternalCompactionJob.class, new ThriftSerializer()); diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java index ed2dd46774a..ce99364ad5e 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java @@ -44,15 +44,17 @@ import org.apache.accumulo.core.dataImpl.TabletIdImpl; import org.apache.accumulo.core.metadata.TabletState; import org.apache.accumulo.core.metrics.flatbuffers.FMetric; +import org.apache.accumulo.core.metrics.flatbuffers.FTag; import org.apache.accumulo.core.metrics.thrift.MetricResponse; import org.apache.accumulo.monitor.next.InformationFetcher.GcServerId; +import org.apache.accumulo.server.metrics.MetricResponseWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.benmanes.caffeine.cache.Cache; import io.micrometer.core.instrument.Clock; -import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Meter.Id; import io.micrometer.core.instrument.Meter.Type; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.cumulative.CumulativeDistributionSummary; @@ -293,20 +295,20 @@ public Set getNotRespondedHosts() { // Summaries of metrics by server type // map of metric name to metric values - private final Map totalCompactorMetrics = + private final Map totalCompactorMetrics = new ConcurrentHashMap<>(); - private final Map totalSServerMetrics = + private final Map totalSServerMetrics = new ConcurrentHashMap<>(); - private final Map totalTServerMetrics = + private final Map totalTServerMetrics = new ConcurrentHashMap<>(); // Summaries of metrics by server type and resource group // map of resource group to metric name to metric values - private final Map> rgCompactorMetrics = + private final Map> rgCompactorMetrics = new ConcurrentHashMap<>(); - private final Map> rgSServerMetrics = + private final Map> rgSServerMetrics = new ConcurrentHashMap<>(); - private final Map> rgTServerMetrics = + private final Map> rgTServerMetrics = new ConcurrentHashMap<>(); // Compaction Information @@ -340,16 +342,23 @@ public void clear() { } private void updateAggregates(final MetricResponse response, - final Map total, - final Map> rg) { + final Map total, + final Map> rg) { - final Map rgMetrics = - rg.computeIfAbsent(response.getResourceGroup(), - (k) -> new ConcurrentHashMap()); + final Map rgMetrics = + rg.computeIfAbsent(response.getResourceGroup(), (k) -> new ConcurrentHashMap<>()); response.getMetrics().forEach((bb) -> { final FMetric fm = FMetric.getRootAsFMetric(bb); final String name = fm.name(); + FTag statisticTag = null; + for (int i = 0; i < fm.tagsLength(); i++) { + FTag t = fm.tags(i); + if (t.key().equals(MetricResponseWrapper.STATISTIC_TAG)) { + statisticTag = t; + break; + } + } double value = fm.dvalue(); if (value == 0.0) { value = fm.ivalue(); @@ -357,13 +366,15 @@ private void updateAggregates(final MetricResponse response, value = fm.lvalue(); } } - final Meter.Id id = new Meter.Id(name, Tags.empty(), null, null, Type.valueOf(fm.type())); + final Id id = new Id(name, + (statisticTag == null) ? Tags.empty() : Tags.of(statisticTag.key(), statisticTag.value()), + null, null, Type.valueOf(fm.type())); total - .computeIfAbsent(name, + .computeIfAbsent(id, (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, DSC, 1.0, false)) .record(value); rgMetrics - .computeIfAbsent(name, + .computeIfAbsent(id, (k) -> new CumulativeDistributionSummary(id, Clock.SYSTEM, DSC, 1.0, false)) .record(value); }); @@ -472,12 +483,12 @@ public Set getCompactorResourceGroupServers(String resourceGroup) { return this.compactors.get(resourceGroup); } - public Map + public Map getCompactorResourceGroupMetricSummary(String resourceGroup) { return this.rgCompactorMetrics.get(resourceGroup); } - public Map getCompactorAllMetricSummary() { + public Map getCompactorAllMetricSummary() { return this.totalCompactorMetrics; } @@ -485,12 +496,12 @@ public Set getSServerResourceGroupServers(String resourceGroup) { return this.sservers.get(resourceGroup); } - public Map + public Map getSServerResourceGroupMetricSummary(String resourceGroup) { return this.rgSServerMetrics.get(resourceGroup); } - public Map getSServerAllMetricSummary() { + public Map getSServerAllMetricSummary() { return this.totalSServerMetrics; } @@ -498,12 +509,12 @@ public Set getTServerResourceGroupServers(String resourceGroup) { return this.tservers.get(resourceGroup); } - public Map + public Map getTServerResourceGroupMetricSummary(String resourceGroup) { return this.rgTServerMetrics.get(resourceGroup); } - public Map getTServerAllMetricSummary() { + public Map getTServerAllMetricSummary() { return this.totalTServerMetrics; } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/IdSerializer.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/IdSerializer.java new file mode 100644 index 00000000000..19f46227979 --- /dev/null +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/serializers/IdSerializer.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.monitor.next.serializers; + +import java.io.IOException; + +import org.apache.accumulo.server.metrics.MetricResponseWrapper; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import io.micrometer.core.instrument.Meter.Id; +import io.micrometer.core.instrument.Tag; + +public class IdSerializer extends JsonSerializer { + + @Override + public void serialize(Id value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + StringBuilder buf = new StringBuilder(); + buf.append(value.getName()); + if (value.getTags() != null && value.getTags().size() > 0) { + for (Tag t : value.getTags()) { + if (t.getKey().endsWith(MetricResponseWrapper.STATISTIC_TAG)) { + buf.append("."); + buf.append(t.getValue()); + } + } + } + + gen.writeFieldName(buf.toString()); + } + +} From 91ef9ddad6cc67ac779ae1a1db404977f571be3e Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Thu, 14 Nov 2024 15:33:53 +0000 Subject: [PATCH 22/22] Removed custom GC server type --- .../monitor/next/InformationFetcher.java | 28 +++---------------- .../monitor/next/SystemInformation.java | 14 ++-------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java index a1285146284..dd7b305c28e 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/InformationFetcher.java @@ -26,7 +26,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -42,13 +41,11 @@ import org.apache.accumulo.core.client.TableNotFoundException; import org.apache.accumulo.core.client.admin.TabletInformation; import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.client.admin.servers.ServerId.Type; import org.apache.accumulo.core.compaction.thrift.CompactionCoordinatorService; import org.apache.accumulo.core.compaction.thrift.TExternalCompaction; import org.apache.accumulo.core.compaction.thrift.TExternalCompactionList; import org.apache.accumulo.core.data.Range; -import org.apache.accumulo.core.lock.ServiceLockData; -import org.apache.accumulo.core.lock.ServiceLockData.ThriftService; -import org.apache.accumulo.core.lock.ServiceLockPaths.ServiceLockPath; import org.apache.accumulo.core.metrics.thrift.MetricResponse; import org.apache.accumulo.core.metrics.thrift.MetricService.Client; import org.apache.accumulo.core.rpc.ThriftUtil; @@ -112,13 +109,6 @@ public Set getVolumes() { } } - static class GcServerId extends ServerId { - private GcServerId(String resourceGroup, String host, int port) { - // TODO: This is a little wonky, Type.GC does not exist in the public API, - super(ServerId.Type.MANAGER, resourceGroup, host, port); - } - } - private class MetricFetcher implements Runnable { private final ServerContext ctx; @@ -279,25 +269,15 @@ public void run() { final SystemInformation summary = new SystemInformation(allMetrics); for (ServerId.Type type : ServerId.Type.values()) { + if (type == Type.MONITOR) { + continue; + } for (ServerId server : this.ctx.instanceOperations().getServers(type)) { futures.add(this.pool.submit(new MetricFetcher(this.ctx, server, summary))); } } ThreadPools.resizePool(pool, () -> Math.max(20, (futures.size() / 20)), poolName); - // GC is not a public type, add it - ServiceLockPath zgcPath = this.ctx.getServerPaths().getGarbageCollector(true); - if (zgcPath != null) { - Optional sld = this.ctx.getZooCache().getLockData(zgcPath); - if (sld.isPresent()) { - String location = sld.orElseThrow().getAddressString(ThriftService.GC); - String resourceGroup = sld.orElseThrow().getGroup(ThriftService.GC); - HostAndPort hp = HostAndPort.fromString(location); - futures.add(this.pool.submit(new MetricFetcher(this.ctx, - new GcServerId(resourceGroup, hp.getHost(), hp.getPort()), summary))); - } - } - // Fetch external compaction information from the Manager futures.add(this.pool.submit(new CompactionListFetcher(summary))); diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java index ce99364ad5e..08e6c038425 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java @@ -46,7 +46,6 @@ import org.apache.accumulo.core.metrics.flatbuffers.FMetric; import org.apache.accumulo.core.metrics.flatbuffers.FTag; import org.apache.accumulo.core.metrics.thrift.MetricResponse; -import org.apache.accumulo.monitor.next.InformationFetcher.GcServerId; import org.apache.accumulo.server.metrics.MetricResponseWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -446,20 +445,13 @@ public void processError(ServerId server) { public void finish() { // Iterate over the metrics allMetrics.asMap().keySet().forEach(serverId -> { - String typeName = serverId.getType().name(); - if (serverId instanceof GcServerId) { - typeName = "GC"; - } deployment.computeIfAbsent(serverId.getResourceGroup(), g -> new HashMap<>()) - .computeIfAbsent(typeName, t -> new ProcessSummary()).addResponded(); + .computeIfAbsent(serverId.getType().name(), t -> new ProcessSummary()).addResponded(); }); problemHosts.forEach(serverId -> { - String typeName = serverId.getType().name(); - if (serverId instanceof GcServerId) { - typeName = "GC"; - } deployment.computeIfAbsent(serverId.getResourceGroup(), g -> new HashMap<>()) - .computeIfAbsent(typeName, t -> new ProcessSummary()).addNotResponded(serverId); + .computeIfAbsent(serverId.getType().name(), t -> new ProcessSummary()) + .addNotResponded(serverId); }); }