diff --git a/common/src/main/java/com/alibaba/nacos/common/utils/TopnCounterMetricsContainer.java b/common/src/main/java/com/alibaba/nacos/common/utils/TopnCounterMetricsContainer.java deleted file mode 100644 index 2f0b4761703..00000000000 --- a/common/src/main/java/com/alibaba/nacos/common/utils/TopnCounterMetricsContainer.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 1999-2022 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.alibaba.nacos.common.utils; - -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * container for top n counter metrics, increment and remove cost O(1) time. - * - * @author liuyixiao - */ -public class TopnCounterMetricsContainer { - - /** - * dataId -> count. - */ - private ConcurrentHashMap dataCount; - - /** - * count -> node. - */ - private ConcurrentHashMap specifiedCountDataIdSets; - - private DoublyLinkedNode dummyHead; - - public TopnCounterMetricsContainer() { - dataCount = new ConcurrentHashMap<>(); - specifiedCountDataIdSets = new ConcurrentHashMap<>(); - dummyHead = new DoublyLinkedNode(null, null, null, -1); - dummyHead.next = new DoublyLinkedNode(null, dummyHead, new ConcurrentHashSet<>(), 0); - specifiedCountDataIdSets.put(0, dummyHead.next); - } - - public List> getTopNCounter(int n) { - List> topnCounter = new LinkedList<>(); - DoublyLinkedNode curr = dummyHead; - while (curr.next != null && topnCounter.size() < n) { - for (String dataId : curr.next.dataSet) { - // use inner AtomicInteger to reflect change to prometheus - topnCounter.add(new Pair<>(dataId, dataCount.get(dataId))); - if (topnCounter.size() == n) { - break; - } - } - curr = curr.next; - } - return topnCounter; - } - - /** - * put(String dataId, 0). - * - * @param dataId data name or data key. - */ - public void put(String dataId) { - put(dataId, 0); - } - - /** - * put new data into container, if already exist, update it. - * this method could be slow (O(N)), most time use increment. - * - * @param dataId data name or data key. - * @param count data count. - */ - public void put(String dataId, int count) { - if (dataCount.containsKey(dataId)) { - removeFromSpecifiedCountDataIdSets(dataId); - dataCount.get(dataId).set(count); - } else { - dataCount.put(dataId, new AtomicInteger(count)); - } - insertIntoSpecifiedCountDataIdSets(dataId, count); - } - - /** - * get data count by dataId. - * - * @param dataId data name or data key. - * @return data count or -1 if not exist. - */ - public int get(String dataId) { - if (dataCount.containsKey(dataId)) { - return dataCount.get(dataId).get(); - } - return -1; - } - - /** - * increment the count of dataId. - * - * @param dataId data name or data key. - */ - public void increment(String dataId) { - if (!dataCount.containsKey(dataId)) { - put(dataId); - } - DoublyLinkedNode prev = removeFromSpecifiedCountDataIdSets(dataId); - int newCount = dataCount.get(dataId).incrementAndGet(); - if (!isDummyHead(prev) && prev.count == newCount) { - insertIntoSpecifiedCountDataIdSets(dataId, prev); - } else { - // prev.count > newCount - DoublyLinkedNode newNode = new DoublyLinkedNode(prev.next, prev, new ConcurrentHashSet<>(), newCount); - if (prev.next != null) { - prev.next.prev = newNode; - } - prev.next = newNode; - newNode.dataSet.add(dataId); - specifiedCountDataIdSets.put(newCount, newNode); - } - } - - /** - * remove data. - * - * @param dataId data name or data key. - * @return data count or null if data is not exist. - */ - public AtomicInteger remove(String dataId) { - if (dataCount.containsKey(dataId)) { - removeFromSpecifiedCountDataIdSets(dataId); - return dataCount.remove(dataId); - } - return null; - } - - /** - * remove all data. - */ - public void removeAll() { - for (String dataId : dataCount.keySet()) { - removeFromSpecifiedCountDataIdSets(dataId); - } - dataCount.clear(); - } - - private DoublyLinkedNode removeFromSpecifiedCountDataIdSets(String dataId) { - int count = dataCount.get(dataId).get(); - DoublyLinkedNode node = specifiedCountDataIdSets.get(count); - node.dataSet.remove(dataId); - // keep the 0 count node. - if (node.dataSet.size() == 0 && node.count != 0) { - node.prev.next = node.next; - if (node.next != null) { - node.next.prev = node.prev; - } - specifiedCountDataIdSets.remove(node.count); - } - return node.prev; - } - - private void insertIntoSpecifiedCountDataIdSets(String dataId, int count) { - if (specifiedCountDataIdSets.containsKey(count)) { - specifiedCountDataIdSets.get(count).dataSet.add(dataId); - } else { - DoublyLinkedNode prev = dummyHead; - while (prev.next != null) { - if (prev.next.count < count) { - break; - } else { - prev = prev.next; - } - } - DoublyLinkedNode newNode = new DoublyLinkedNode(prev.next, prev, new ConcurrentHashSet<>(), count); - if (prev.next != null) { - prev.next.prev = newNode; - } - prev.next = newNode; - newNode.dataSet.add(dataId); - specifiedCountDataIdSets.put(count, newNode); - } - } - - private void insertIntoSpecifiedCountDataIdSets(String dataId, DoublyLinkedNode targetSet) { - targetSet.dataSet.add(dataId); - } - - private boolean isDummyHead(DoublyLinkedNode node) { - return node.count == -1; - } - - private class DoublyLinkedNode { - - public DoublyLinkedNode next; - - public DoublyLinkedNode prev; - - public ConcurrentHashSet dataSet; - - public int count; - - public DoublyLinkedNode(DoublyLinkedNode next, DoublyLinkedNode prev, ConcurrentHashSet dataSet, int count) { - this.next = next; - this.prev = prev; - this.dataSet = dataSet; - this.count = count; - } - } -} diff --git a/common/src/test/java/com/alibaba/nacos/common/utils/TopnCounterMetricsContainerTest.java b/common/src/test/java/com/alibaba/nacos/common/utils/TopnCounterMetricsContainerTest.java deleted file mode 100644 index b2d867b4d47..00000000000 --- a/common/src/test/java/com/alibaba/nacos/common/utils/TopnCounterMetricsContainerTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 1999-2022 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.alibaba.nacos.common.utils; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * unit test for TopNCounterMetricsContainer. - * - * @author liuyixiao - */ -public class TopnCounterMetricsContainerTest { - - private TopnCounterMetricsContainer topnCounterMetricsContainer; - - @Before - public void setUp() { - topnCounterMetricsContainer = new TopnCounterMetricsContainer(); - } - - @Test - public void testPut() { - topnCounterMetricsContainer.put("test"); - Assert.assertEquals(0, topnCounterMetricsContainer.get("test")); - topnCounterMetricsContainer.put("test1", 1); - Assert.assertEquals(1, topnCounterMetricsContainer.get("test1")); - } - - @Test - public void testIncrement() { - topnCounterMetricsContainer.put("test", 0); - topnCounterMetricsContainer.increment("test"); - Assert.assertEquals(1, topnCounterMetricsContainer.get("test")); - } - - @Test - public void testRemove() { - topnCounterMetricsContainer.put("test"); - Assert.assertEquals(0, topnCounterMetricsContainer.get("test")); - topnCounterMetricsContainer.remove("test"); - Assert.assertEquals(-1, topnCounterMetricsContainer.get("test")); - } - - @Test - public void testRemoveAll() { - topnCounterMetricsContainer.put("test"); - topnCounterMetricsContainer.put("test1"); - topnCounterMetricsContainer.put("test2"); - topnCounterMetricsContainer.removeAll(); - Assert.assertEquals(-1, topnCounterMetricsContainer.get("test")); - Assert.assertEquals(-1, topnCounterMetricsContainer.get("test1")); - Assert.assertEquals(-1, topnCounterMetricsContainer.get("test2")); - } - - @Test - public void testGetTopNCounterAndRemoveAll() { - final int N = 10; - String dataIds = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - List> dataList = new ArrayList<>(); - for (int i = 0; i < dataIds.length(); i++) { - topnCounterMetricsContainer.put(dataIds.substring(i, i + 1)); - dataList.add(new Pair<>(dataIds.substring(i, i + 1), new AtomicInteger())); - } - Random random = new Random(); - for (int i = 0; i < 10000; i++) { - int j = random.nextInt(dataIds.length()); - topnCounterMetricsContainer.increment(dataIds.substring(j, j + 1)); - dataList.get(j).getSecond().incrementAndGet(); - } - boolean right = true; - Collections.sort(dataList, (a, b) -> b.getSecond().get() - a.getSecond().get()); - List> result = topnCounterMetricsContainer.getTopNCounter(N); - for (Pair item : result) { - // ensure every top N count is greater than (N+1)th greatest. - if (item.getSecond().get() < dataList.get(N).getSecond().get()) { - right = false; - break; - } - } - Assert.assertTrue(right); - topnCounterMetricsContainer.removeAll(); - for (int i = 0; i < dataIds.length(); i++) { - Assert.assertEquals(-1, topnCounterMetricsContainer.get(dataIds.substring(i, i + 1))); - } - Assert.assertEquals(0, topnCounterMetricsContainer.getTopNCounter(N).size()); - } -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/monitor/ConfigDynamicMeterRefreshService.java b/config/src/main/java/com/alibaba/nacos/config/server/monitor/ConfigDynamicMeterRefreshService.java index fcb0b7e3f18..8276b63f033 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/monitor/ConfigDynamicMeterRefreshService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/monitor/ConfigDynamicMeterRefreshService.java @@ -59,6 +59,6 @@ public void refreshTopnConfigChangeCount() { */ @Scheduled(cron = "0 0 0 ? * 1") public void resetTopnConfigChangeCount() { - MetricsMonitor.getConfigChangeCount().removeAll(); + MetricsMonitor.getConfigChangeCount().reset(); } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/monitor/MetricsMonitor.java b/config/src/main/java/com/alibaba/nacos/config/server/monitor/MetricsMonitor.java index 2cc9f734e2d..aca6c68e9f1 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/monitor/MetricsMonitor.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/monitor/MetricsMonitor.java @@ -16,8 +16,8 @@ package com.alibaba.nacos.config.server.monitor; -import com.alibaba.nacos.common.utils.TopnCounterMetricsContainer; import com.alibaba.nacos.core.monitor.NacosMeterRegistryCenter; +import com.alibaba.nacos.core.monitor.topn.StringTopNCounter; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.ImmutableTag; import io.micrometer.core.instrument.Tag; @@ -73,7 +73,7 @@ public class MetricsMonitor { /** * config change count. */ - private static TopnCounterMetricsContainer configChangeCount = new TopnCounterMetricsContainer(); + private static StringTopNCounter configChangeCount = new StringTopNCounter(); static { ImmutableTag immutableTag = new ImmutableTag("module", "config"); @@ -166,7 +166,7 @@ public static AtomicInteger getConfigSubscriberMonitor(String version) { return configSubscriber.get(version); } - public static TopnCounterMetricsContainer getConfigChangeCount() { + public static StringTopNCounter getConfigChangeCount() { return configChangeCount; } diff --git a/core/src/main/java/com/alibaba/nacos/core/monitor/topn/BaseTopNCounter.java b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/BaseTopNCounter.java new file mode 100644 index 00000000000..dd31f0f88f4 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/BaseTopNCounter.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.monitor.topn; + +import com.alibaba.nacos.common.utils.Pair; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Nacos base topN counter. + * + * @author xiweng.yy + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public abstract class BaseTopNCounter { + + private final Comparator> comparator; + + protected ConcurrentMap dataCount; + + protected BaseTopNCounter() { + dataCount = new ConcurrentHashMap<>(); + this.comparator = Comparator.comparingInt(value -> value.getSecond().get()); + } + + /** + * Get topN counter by PriorityQueue. + * + * @param topN topN + * @return topN counter + */ + public List> getTopNCounter(int topN) { + if (!checkEnabled()) { + reset(); + return Collections.emptyList(); + } + ConcurrentMap snapshot = dataCount; + dataCount = new ConcurrentHashMap<>(1); + FixedSizePriorityQueue> queue = new FixedSizePriorityQueue<>(topN, comparator); + for (T t : snapshot.keySet()) { + queue.offer(Pair.with(keyToString(t), snapshot.get(t))); + } + return queue.toList(); + } + + /** + * Transfer key from type T to String. + * + * @param t key + * @return String + */ + protected abstract String keyToString(T t); + + /** + * Increment 1 count for target key. + * + * @param t key + */ + public void increment(T t) { + if (checkEnabled()) { + increment(t, 1); + } + } + + /** + * Increment specified count for target key. + * + * @param t key + * @param count count + */ + public void increment(T t, int count) { + if (checkEnabled()) { + dataCount.computeIfAbsent(t, k -> new AtomicInteger(0)).addAndGet(count); + } + } + + /** + * Directly set count for target key. + * + * @param t key + * @param count new count + */ + public void set(T t, int count) { + if (checkEnabled()) { + dataCount.computeIfAbsent(t, k -> new AtomicInteger(0)).set(count); + } + } + + public void reset() { + dataCount.clear(); + } + + protected boolean checkEnabled() { + return TopNConfig.getInstance().isEnabled(); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/monitor/topn/FixedSizePriorityQueue.java b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/FixedSizePriorityQueue.java new file mode 100644 index 00000000000..2023d4d5344 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/FixedSizePriorityQueue.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.monitor.topn; + +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +/** + * Fixed size priority queue. + * + * @author xiweng.yy + */ +@SuppressWarnings("PMD.UndefineMagicConstantRule") +public class FixedSizePriorityQueue { + + private Object[] elements; + + private int size; + + private Comparator comparator; + + public FixedSizePriorityQueue(int capacity, Comparator comparator) { + elements = new Object[capacity]; + size = 0; + this.comparator = comparator; + } + + /** + * Offer queue, if queue is full and offer element is not bigger than the first element in queue, offer element will + * be ignored. + * + * @param element new element. + */ + public void offer(T element) { + if (size == elements.length) { + if (comparator.compare(element, (T) elements[0]) > 0) { + elements[0] = element; + siftDown(); + } + } else { + elements[size] = element; + siftUp(size); + size++; + } + } + + private void siftUp(int index) { + while (index > 0) { + int parentIndex = (index - 1) / 2; + if (comparator.compare((T) elements[index], (T) elements[parentIndex]) > 0) { + break; + } + swap(index, parentIndex); + index = parentIndex; + } + } + + private void siftDown() { + int index = 0; + while (index * 2 + 1 < size) { + int leftChild = index * 2 + 1; + int rightChild = index * 2 + 2; + int minChildIndex = leftChild; + if (rightChild < size && comparator.compare((T) elements[rightChild], (T) elements[leftChild]) < 0) { + minChildIndex = rightChild; + } + if (comparator.compare((T) elements[index], (T) elements[minChildIndex]) < 0) { + break; + } + swap(index, minChildIndex); + index = minChildIndex; + } + } + + private void swap(int i, int j) { + Object temp = elements[i]; + elements[i] = elements[j]; + elements[j] = temp; + } + + /** + * Transfer queue to list without order. + * + * @return list + */ + public List toList() { + List result = new LinkedList<>(); + for (int i = 0; i < size; i++) { + result.add((T) elements[i]); + } + return result; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/monitor/topn/StringTopNCounter.java b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/StringTopNCounter.java new file mode 100644 index 00000000000..d6ae6823c0a --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/StringTopNCounter.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.monitor.topn; + +/** + * String key topN counter. + * + * @author xiweng.yy + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class StringTopNCounter extends BaseTopNCounter { + + @Override + protected String keyToString(String s) { + return s; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/monitor/topn/TopNConfig.java b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/TopNConfig.java new file mode 100644 index 00000000000..f6ab940accc --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/monitor/topn/TopNConfig.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.monitor.topn; + +import com.alibaba.nacos.core.config.AbstractDynamicConfig; +import com.alibaba.nacos.sys.env.EnvUtil; + +import java.util.concurrent.TimeUnit; + +/** + * TopN configurations. + * + * @author xiweng.yy + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class TopNConfig extends AbstractDynamicConfig { + + private static final String TOP_N = "topN"; + + private static final TopNConfig INSTANCE = new TopNConfig(); + + private static final String TOP_N_PREFIX = "nacos.core.monitor.topn."; + + private static final String ENABLED_KEY = TOP_N_PREFIX + "enabled"; + + private static final String COUNT_KEY = TOP_N_PREFIX + "count"; + + private static final String INTERNAL_MS_KEY = TOP_N_PREFIX + "internalMs"; + + private static final boolean DEFAULT_ENABLED = true; + + private static final int DEFAULT_COUNT = 10; + + private static final long DEFAULT_INTERNAL_MS = TimeUnit.SECONDS.toMillis(30); + + private boolean enabled; + + private int topNCount; + + private long internalMs; + + private TopNConfig() { + super(TOP_N); + } + + @Override + protected void getConfigFromEnv() { + enabled = EnvUtil.getProperty(ENABLED_KEY, Boolean.class, DEFAULT_ENABLED); + topNCount = EnvUtil.getProperty(COUNT_KEY, Integer.class, DEFAULT_COUNT); + internalMs = EnvUtil.getProperty(INTERNAL_MS_KEY, Long.class, DEFAULT_INTERNAL_MS); + } + + @Override + protected String printConfig() { + return toString(); + } + + @Override + public String toString() { + return "TopNConfig{" + "enabled=" + enabled + ", topNCount=" + topNCount + ", internalMs=" + internalMs + '}'; + } + + public static TopNConfig getInstance() { + return INSTANCE; + } + + public boolean isEnabled() { + return enabled; + } + + public int getTopNCount() { + return topNCount; + } + + public long getInternalMs() { + return internalMs; + } +} diff --git a/core/src/test/java/com/alibaba/nacos/core/monitor/topn/FixedSizePriorityQueueTest.java b/core/src/test/java/com/alibaba/nacos/core/monitor/topn/FixedSizePriorityQueueTest.java new file mode 100644 index 00000000000..c6ef68ee62b --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/monitor/topn/FixedSizePriorityQueueTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.monitor.topn; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class FixedSizePriorityQueueTest { + + @Test + public void testOfferEmpty() { + FixedSizePriorityQueue queue = new FixedSizePriorityQueue<>(10, Comparator.naturalOrder()); + List list = queue.toList(); + assertTrue(list.isEmpty()); + } + + @Test + public void testOfferLessThanSize() { + FixedSizePriorityQueue queue = new FixedSizePriorityQueue<>(10, Comparator.naturalOrder()); + for (int i = 0; i < 5; i++) { + queue.offer(i); + } + List list = queue.toList(); + assertEquals(5, list.size()); + for (int i = 0; i < 5; i++) { + assertTrue(list.contains(i)); + } + } + + @Test + public void testOfferMoreThanSizeWithIncreasing() { + FixedSizePriorityQueue queue = new FixedSizePriorityQueue<>(10, Comparator.naturalOrder()); + for (int i = 0; i < 15; i++) { + queue.offer(i); + } + List list = queue.toList(); + assertEquals(10, list.size()); + for (int i = 14; i > 4; i--) { + assertTrue(list.contains(i)); + } + } + + @Test + public void testOfferMoreThanSizeWithDecreasing() { + FixedSizePriorityQueue queue = new FixedSizePriorityQueue<>(10, Comparator.naturalOrder()); + for (int i = 14; i > 0; i--) { + queue.offer(i); + } + List list = queue.toList(); + assertEquals(10, list.size()); + for (int i = 14; i > 4; i--) { + assertTrue(list.contains(i)); + } + } + + @Test + public void testOfferMoreThanSizeWithShuffle() { + List testCase = new ArrayList<>(50); + for (int i = 0; i < 50; i++) { + testCase.add(i); + } + Collections.shuffle(testCase); + FixedSizePriorityQueue queue = new FixedSizePriorityQueue<>(10, Comparator.naturalOrder()); + testCase.forEach(queue::offer); + List list = queue.toList(); + assertEquals(10, list.size()); + for (int i = 49; i > 39; i--) { + assertTrue(list.contains(i)); + } + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/monitor/topn/StringTopNCounterTest.java b/core/src/test/java/com/alibaba/nacos/core/monitor/topn/StringTopNCounterTest.java new file mode 100644 index 00000000000..c6c8e38c5f3 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/monitor/topn/StringTopNCounterTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.monitor.topn; + +import com.alibaba.nacos.common.event.ServerConfigChangeEvent; +import com.alibaba.nacos.common.utils.Pair; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.env.MockEnvironment; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * unit test for TopNCounterMetricsContainer. + * + * @author liuyixiao + */ +public class StringTopNCounterTest { + + private StringTopNCounter stringTopNCounter; + + @Before + public void setUp() { + stringTopNCounter = new StringTopNCounter(); + } + + @After + public void tearDown() { + stringTopNCounter.reset(); + EnvUtil.setEnvironment(new MockEnvironment()); + TopNConfig.getInstance().onEvent(new ServerConfigChangeEvent()); + } + + @Test + public void testSet() { + stringTopNCounter.set("test1", 1); + List> actual = stringTopNCounter.getTopNCounter(10); + assertTopNCounter(actual, 1, new String[] {"test1"}, new Integer[] {1}); + } + + @Test + public void testIncrement() { + stringTopNCounter.set("test", 0); + assertTopNCounter(stringTopNCounter.getTopNCounter(10), 1, new String[] {"test"}, new Integer[] {0}); + stringTopNCounter.increment("test"); + assertTopNCounter(stringTopNCounter.getTopNCounter(10), 1, new String[] {"test"}, new Integer[] {1}); + } + + @Test + public void testReset() { + stringTopNCounter.set("test", 1); + stringTopNCounter.set("test1", 2); + stringTopNCounter.set("test2", 3); + assertTopNCounter(stringTopNCounter.getTopNCounter(10), 3, new String[] {"test2", "test1", "test"}, + new Integer[] {3, 2, 1}); + stringTopNCounter.reset(); + assertTopNCounter(stringTopNCounter.getTopNCounter(10), 0, new String[] {}, new Integer[] {}); + } + + @Test + public void testGetTopNCounter() { + for (int i = 0; i < 20; i++) { + stringTopNCounter.set("test" + i, i); + } + assertTopNCounter(stringTopNCounter.getTopNCounter(10), 10, new String[] {"test19", "test18", "test17", "test16", + "test15", "test14", "test13", "test12", "test11", "test10"}, new Integer[] {19, 18, 17, 16, 15, 14, 13, 12, 11, 10}); + } + + @Test + public void testForTopnDisabled() { + MockEnvironment env = new MockEnvironment(); + env.setProperty("nacos.core.monitor.topn.enabled", "false"); + EnvUtil.setEnvironment(env); + TopNConfig.getInstance().onEvent(new ServerConfigChangeEvent()); + stringTopNCounter.set("test", 1); + stringTopNCounter.set("test1", 2); + stringTopNCounter.set("test2", 3); + assertTopNCounter(stringTopNCounter.getTopNCounter(10), 0, new String[] {}, new Integer[] {}); + } + + private void assertTopNCounter(List> actual, int size, String[] keys, Integer[] value) { + Assert.assertEquals(size, actual.size()); + for (int i = 0; i < size; i++) { + Assert.assertTrue(Arrays.asList(keys).contains(actual.get(i).getFirst())); + Assert.assertTrue(Arrays.asList(value).contains(actual.get(i).getSecond().get())); + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/monitor/MetricsMonitor.java b/naming/src/main/java/com/alibaba/nacos/naming/monitor/MetricsMonitor.java index 3b836fd5374..4cd2be774b7 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/monitor/MetricsMonitor.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/monitor/MetricsMonitor.java @@ -16,10 +16,10 @@ package com.alibaba.nacos.naming.monitor; +import com.alibaba.nacos.core.monitor.NacosMeterRegistryCenter; import com.alibaba.nacos.naming.core.v2.pojo.BatchInstancePublishInfo; import com.alibaba.nacos.naming.core.v2.pojo.InstancePublishInfo; -import com.alibaba.nacos.common.utils.TopnCounterMetricsContainer; -import com.alibaba.nacos.core.monitor.NacosMeterRegistryCenter; +import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.misc.Loggers; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.ImmutableTag; @@ -90,7 +90,7 @@ public class MetricsMonitor { /** * topn service change count. */ - private final TopnCounterMetricsContainer serviceChangeCount = new TopnCounterMetricsContainer(); + private final ServiceTopNCounter serviceChangeCount = new ServiceTopNCounter(); private MetricsMonitor() { for (Field each : MetricsMonitor.class.getDeclaredFields()) { @@ -110,18 +110,18 @@ private MetricsMonitor() { List tags = new ArrayList<>(); tags.add(new ImmutableTag("version", "v1")); NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_naming_subscriber", tags, namingSubscriber.get("v1")); - + tags = new ArrayList<>(); tags.add(new ImmutableTag("version", "v2")); NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_naming_subscriber", tags, namingSubscriber.get("v2")); - + namingPublisher.put("v1", new AtomicInteger(0)); namingPublisher.put("v2", new AtomicInteger(0)); - + tags = new ArrayList<>(); tags.add(new ImmutableTag("version", "v1")); NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_naming_publisher", tags, namingPublisher.get("v1")); - + tags = new ArrayList<>(); tags.add(new ImmutableTag("version", "v2")); NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_naming_publisher", tags, namingPublisher.get("v2")); @@ -210,7 +210,7 @@ public static AtomicInteger getNamingPublisher(String version) { return INSTANCE.namingPublisher.get(version); } - public static TopnCounterMetricsContainer getServiceChangeCount() { + public static ServiceTopNCounter getServiceChangeCount() { return INSTANCE.serviceChangeCount; } @@ -251,8 +251,8 @@ public static void decrementSubscribeCount() { INSTANCE.subscriberCount.decrementAndGet(); } - public static void incrementServiceChangeCount(String namespace, String group, String name) { - INSTANCE.serviceChangeCount.increment(namespace + "@" + group + "@" + name); + public static void incrementServiceChangeCount(Service service) { + INSTANCE.serviceChangeCount.increment(service); } public static Counter getDiskException() { @@ -260,15 +260,18 @@ public static Counter getDiskException() { } public static Counter getLeaderSendBeatFailedException() { - return NacosMeterRegistryCenter.counter(METER_REGISTRY, "nacos_exception", "module", "naming", "name", "leaderSendBeatFailed"); + return NacosMeterRegistryCenter + .counter(METER_REGISTRY, "nacos_exception", "module", "naming", "name", "leaderSendBeatFailed"); } /** * increment IpCount when use batchRegister instance. - * @param old old instancePublishInfo + * + * @param old old instancePublishInfo * @param instancePublishInfo must be BatchInstancePublishInfo */ - public static void incrementIpCountWithBatchRegister(InstancePublishInfo old, BatchInstancePublishInfo instancePublishInfo) { + public static void incrementIpCountWithBatchRegister(InstancePublishInfo old, + BatchInstancePublishInfo instancePublishInfo) { int newSize = instancePublishInfo.getInstancePublishInfos().size(); if (null == old) { // First time increment batchPublishInfo, add all into metrics. @@ -285,6 +288,7 @@ public static void incrementIpCountWithBatchRegister(InstancePublishInfo old, Ba /** * decrement IpCount when use batchRegister instance. + * * @param instancePublishInfo must be BatchInstancePublishInfo */ public static void decrementIpCountWithBatchRegister(InstancePublishInfo instancePublishInfo) { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/monitor/NamingDynamicMeterRefreshService.java b/naming/src/main/java/com/alibaba/nacos/naming/monitor/NamingDynamicMeterRefreshService.java index 7c60d04ab4b..19fb0ccb186 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/monitor/NamingDynamicMeterRefreshService.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/monitor/NamingDynamicMeterRefreshService.java @@ -50,9 +50,9 @@ public void refreshTopnServiceChangeCount() { for (Pair serviceChangeCount : topnServiceChangeCount) { List tags = new ArrayList<>(); tags.add(new ImmutableTag("service", serviceChangeCount.getFirst())); - NacosMeterRegistryCenter.gauge(TOPN_SERVICE_CHANGE_REGISTRY, "service_change_count", tags, serviceChangeCount.getSecond()); + NacosMeterRegistryCenter + .gauge(TOPN_SERVICE_CHANGE_REGISTRY, "service_change_count", tags, serviceChangeCount.getSecond()); } - MetricsMonitor.getServiceChangeCount().removeAll(); } /** @@ -60,6 +60,6 @@ public void refreshTopnServiceChangeCount() { */ @Scheduled(cron = "0 0 0 ? * 1") public void resetTopnServiceChangeCount() { - MetricsMonitor.getServiceChangeCount().removeAll(); + MetricsMonitor.getServiceChangeCount().reset(); } } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/monitor/ServiceTopNCounter.java b/naming/src/main/java/com/alibaba/nacos/naming/monitor/ServiceTopNCounter.java new file mode 100644 index 00000000000..4f72360e59e --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/monitor/ServiceTopNCounter.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.naming.monitor; + +import com.alibaba.nacos.core.monitor.topn.BaseTopNCounter; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; + +/** + * Service topN counter. + * + * @author xiweng.yy + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class ServiceTopNCounter extends BaseTopNCounter { + + public ServiceTopNCounter() { + super(); + } + + @Override + protected String keyToString(Service service) { + return service.getNamespace() + UtilsAndCommons.NAMESPACE_SERVICE_CONNECTOR + service.getGroupedServiceName(); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2Impl.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2Impl.java index 6933779d0a5..9dd25664f54 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2Impl.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2Impl.java @@ -118,7 +118,7 @@ public void onEvent(Event event) { ServiceEvent.ServiceChangedEvent serviceChangedEvent = (ServiceEvent.ServiceChangedEvent) event; Service service = serviceChangedEvent.getService(); delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay())); - MetricsMonitor.incrementServiceChangeCount(service.getNamespace(), service.getGroup(), service.getName()); + MetricsMonitor.incrementServiceChangeCount(service); } else if (event instanceof ServiceEvent.ServiceSubscribedEvent) { // If service is subscribed by one client, only push this client. ServiceEvent.ServiceSubscribedEvent subscribedEvent = (ServiceEvent.ServiceSubscribedEvent) event;