diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/config/ApplicationMapModule.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/config/ApplicationMapModule.java index e2cc3beca7216..05eea852534ef 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/config/ApplicationMapModule.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/config/ApplicationMapModule.java @@ -25,6 +25,8 @@ import com.navercorp.pinpoint.web.applicationmap.appender.server.ServerInfoAppenderFactory; import com.navercorp.pinpoint.web.applicationmap.map.ApplicationsMapCreatorFactory; import com.navercorp.pinpoint.web.applicationmap.map.LinkSelectorFactory; +import com.navercorp.pinpoint.web.applicationmap.map.processor.ApplicationLimiterProcessorFactory; +import com.navercorp.pinpoint.web.applicationmap.map.processor.LinkDataMapProcessor; import com.navercorp.pinpoint.web.applicationmap.service.LinkDataMapService; import com.navercorp.pinpoint.web.dao.HostApplicationMapDao; import com.navercorp.pinpoint.web.security.ServerMapDataFilter; @@ -34,6 +36,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -45,6 +48,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; +import java.util.function.Supplier; @Configuration @ComponentScan(basePackages = { @@ -80,12 +84,18 @@ public ApplicationsMapCreatorFactory applicationsMapCreatorFactory(@Qualifier("a return new ApplicationsMapCreatorFactory(executor); } + @Bean + public Supplier applicationLimiterProcessorFactory(@Value("${pinpoint.server-map.read-limit:100}") int limit) { + return new ApplicationLimiterProcessorFactory(limit); + } + @Bean public LinkSelectorFactory linkSelectorFactory(LinkDataMapService linkDataMapService, ApplicationsMapCreatorFactory applicationsMapCreatorFactory, HostApplicationMapDao hostApplicationMapDao, - Optional serverMapDataFilter) { - return new LinkSelectorFactory(linkDataMapService, applicationsMapCreatorFactory, hostApplicationMapDao, serverMapDataFilter); + Optional serverMapDataFilter, + Supplier applicationLimiterProcessorFactory) { + return new LinkSelectorFactory(linkDataMapService, applicationsMapCreatorFactory, hostApplicationMapDao, serverMapDataFilter, applicationLimiterProcessorFactory); } @Bean diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorFactory.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorFactory.java index ee93b45fac1b7..c25ae301dc7bb 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorFactory.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorFactory.java @@ -27,6 +27,7 @@ import java.util.Objects; import java.util.Optional; +import java.util.function.Supplier; /** * @author HyunGil Jeong @@ -41,16 +42,19 @@ public class LinkSelectorFactory { private final HostApplicationMapDao hostApplicationMapDao; private final ServerMapDataFilter serverMapDataFilter; + private final Supplier applicationLimiterProcessorFactory; public LinkSelectorFactory( LinkDataMapService linkDataMapService, ApplicationsMapCreatorFactory applicationsMapCreatorFactory, HostApplicationMapDao hostApplicationMapDao, - Optional serverMapDataFilter) { + Optional serverMapDataFilter, + Supplier applicationLimiterProcessorFactory) { this.linkDataMapService = Objects.requireNonNull(linkDataMapService, "linkDataMapService"); this.applicationsMapCreatorFactory = Objects.requireNonNull(applicationsMapCreatorFactory, "applicationsMapCreatorFactory"); this.hostApplicationMapDao = Objects.requireNonNull(hostApplicationMapDao, "hostApplicationMapDao"); this.serverMapDataFilter = Objects.requireNonNull(serverMapDataFilter, "serverMapDataFilter").orElse(null); + this.applicationLimiterProcessorFactory = Objects.requireNonNull(applicationLimiterProcessorFactory, "applicationLimiterProcessorFactory"); } public LinkSelector createLinkSelector(LinkSelectorType linkSelectorType) { @@ -58,9 +62,11 @@ public LinkSelector createLinkSelector(LinkSelectorType linkSelectorType) { } public LinkSelector createLinkSelector(LinkSelectorType linkSelectorType, LinkDataMapProcessor outLinkProcessor, LinkDataMapProcessor inLinkProcessor) { + VirtualLinkMarker virtualLinkMarker = new VirtualLinkMarker(); VirtualLinkHandler virtualLinkHandler = new VirtualLinkHandler(linkDataMapService, virtualLinkMarker); + LinkDataMapProcessors outLinkProcessors = newOutLinkProcessors(outLinkProcessor, virtualLinkMarker); LinkDataMapProcessors inLinkProcessors = newInLinkProcessor(inLinkProcessor); @@ -78,12 +84,14 @@ public LinkSelector createLinkSelector(LinkSelectorType linkSelectorType, LinkDa private LinkDataMapProcessors newInLinkProcessor(LinkDataMapProcessor inLinkDataMapProcessor) { LinkDataMapProcessors.Builder inLinkBuilder = LinkDataMapProcessors.newBuilder(); + inLinkBuilder.addLinkProcessor(this.applicationLimiterProcessorFactory.get()); inLinkBuilder.addLinkProcessor(inLinkDataMapProcessor); return inLinkBuilder.build(); } private LinkDataMapProcessors newOutLinkProcessors(LinkDataMapProcessor outLinkDataMapProcessor, VirtualLinkMarker virtualLinkMarker) { LinkDataMapProcessors.Builder outLinkBuilder = LinkDataMapProcessors.newBuilder(); + outLinkBuilder.addLinkProcessor(this.applicationLimiterProcessorFactory.get()); outLinkBuilder.addLinkProcessor(new RpcCallProcessor(hostApplicationMapDao, virtualLinkMarker)); outLinkBuilder.addLinkProcessor(outLinkDataMapProcessor); return outLinkBuilder.build(); diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessor.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessor.java new file mode 100644 index 0000000000000..dad8667a91038 --- /dev/null +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessor.java @@ -0,0 +1,99 @@ +/* + * Copyright 2023 NAVER Corp. + * + * 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.navercorp.pinpoint.web.applicationmap.map.processor; + +import com.navercorp.pinpoint.common.server.util.time.Range; +import com.navercorp.pinpoint.web.applicationmap.link.LinkDirection; +import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkData; +import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkDataMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.concurrent.GuardedBy; +import java.util.Collection; + +/** + * @author HyunGil Jeong + */ +public class ApplicationLimiterProcessor implements LinkDataMapProcessor { + + private final Logger logger = LogManager.getLogger(this.getClass()); + + private static final int BYPASS = Integer.MAX_VALUE; + private static final int EMPTY = 0; + + private final int maxLinkSize; + + @GuardedBy("this") + private volatile int counter = 0; + + + public ApplicationLimiterProcessor(int maxLinkSize) { + this.maxLinkSize = maxLinkSize; + } + + boolean limitReached() { + return maxLinkSize < counter; + } + + + @Override + public LinkDataMap processLinkDataMap(LinkDirection direction, LinkDataMap linkDataMap, Range range) { + final int remain = remain(linkDataMap); + if (remain == BYPASS) { + return linkDataMap; + } + if (remain == EMPTY) { + return new LinkDataMap(); + } + logger.debug("{} Remain {}-->{}, link count {}", direction, linkDataMap, linkDataMap.size(), remain); + Collection linkDataList = linkDataMap.getLinkDataList(); + + LinkDataMap copy = new LinkDataMap(linkDataMap.getTimeWindow()); + int i = 0; + for (LinkData linkData : linkDataList) { + logger.debug("add link {}", linkData); + copy.addLinkData(linkData); + if (++i >= remain) { + logger.debug("break {}/{}", i, remain); + break; + } + } + return copy; + } + + private int remain(LinkDataMap linkDataMap) { + final int size = linkDataMap.size(); + + synchronized (this) { + final int counter = this.counter; + final int current = counter + size; + this.counter += size; + if (this.maxLinkSize >= current) { + return BYPASS; + } + final int remain = this.maxLinkSize - counter; + if (remain > 0) { + return remain; + } + // empty; + return EMPTY; + } + } + +} diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessorFactory.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessorFactory.java new file mode 100644 index 0000000000000..fb41fe1a4896b --- /dev/null +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessorFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 NAVER Corp. + * + * 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.navercorp.pinpoint.web.applicationmap.map.processor; + +import java.util.function.Supplier; + +public class ApplicationLimiterProcessorFactory implements Supplier { + + private final int limit; + + public ApplicationLimiterProcessorFactory(int limit) { + this.limit = limit; + } + + @Override + public LinkDataMapProcessor get() { + if (limit == -1) { + return LinkDataMapProcessor.NO_OP; + } + return new ApplicationLimiterProcessor(limit); + } +} diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/LinkDataMapProcessors.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/LinkDataMapProcessors.java index 99d510e6f7612..676a0474d03c5 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/LinkDataMapProcessors.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/map/processor/LinkDataMapProcessors.java @@ -58,6 +58,9 @@ public static class Builder { public void addLinkProcessor(LinkDataMapProcessor linkProcessor) { Objects.requireNonNull(linkProcessor, "linkProcessor"); + if (LinkDataMapProcessor.NO_OP == linkProcessor) { + return; + } linkDataMapProcessors.add(linkProcessor); } diff --git a/web/src/test/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorTestBase.java b/web/src/test/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorTestBase.java index 544da6f4cce82..76418e8c5cbbf 100644 --- a/web/src/test/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorTestBase.java +++ b/web/src/test/java/com/navercorp/pinpoint/web/applicationmap/map/LinkSelectorTestBase.java @@ -22,6 +22,7 @@ import com.navercorp.pinpoint.common.trace.ServiceTypeFactory; import com.navercorp.pinpoint.common.trace.ServiceTypeProperty; import com.navercorp.pinpoint.web.applicationmap.link.LinkKey; +import com.navercorp.pinpoint.web.applicationmap.map.processor.LinkDataMapProcessor; import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkCallData; import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkData; import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkDataDuplexMap; @@ -75,7 +76,7 @@ public abstract class LinkSelectorTestBase { public void setUp() throws Exception { this.linkDataMapService = mock(LinkDataMapService.class); this.hostApplicationMapDao = mock(HostApplicationMapDao.class); - this.linkSelectorFactory = new LinkSelectorFactory(linkDataMapService, applicationsMapCreatorFactory, hostApplicationMapDao, Optional.empty()); + this.linkSelectorFactory = new LinkSelectorFactory(linkDataMapService, applicationsMapCreatorFactory, hostApplicationMapDao, Optional.empty(), () -> LinkDataMapProcessor.NO_OP); } @AfterEach diff --git a/web/src/test/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessorTest.java b/web/src/test/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessorTest.java new file mode 100644 index 0000000000000..7402a0c14cb3a --- /dev/null +++ b/web/src/test/java/com/navercorp/pinpoint/web/applicationmap/map/processor/ApplicationLimiterProcessorTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 NAVER Corp. + * + * 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.navercorp.pinpoint.web.applicationmap.map.processor; + +import com.navercorp.pinpoint.common.server.util.time.Range; +import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.web.applicationmap.link.LinkDirection; +import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkData; +import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkDataMap; +import com.navercorp.pinpoint.web.vo.Application; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ApplicationLimiterProcessorTest { + + Application application1 = new Application("test1", ServiceType.TEST); + Application application2 = new Application("test2", ServiceType.TEST); + Range between = Range.between(10, 100); + + @Test + void limitReached() { + + ApplicationLimiterProcessor processor = new ApplicationLimiterProcessor(2); + LinkDataMap linkDataMap = new LinkDataMap(); + linkDataMap.addLinkData(new LinkData(application1, application1)); + + processor.processLinkDataMap(LinkDirection.IN_LINK, linkDataMap, between); + Assertions.assertFalse(processor.limitReached()); + + processor.processLinkDataMap(LinkDirection.IN_LINK, linkDataMap, between); + Assertions.assertFalse(processor.limitReached()); + + + processor.processLinkDataMap(LinkDirection.IN_LINK, linkDataMap, between); + Assertions.assertTrue(processor.limitReached()); + } + + + @Test + void limitReached_2() { + + ApplicationLimiterProcessor processor = new ApplicationLimiterProcessor(2); + LinkDataMap linkDataMap = new LinkDataMap(); + linkDataMap.addLinkData(new LinkData(application1, application1)); + + processor.processLinkDataMap(LinkDirection.IN_LINK, linkDataMap, between); + Assertions.assertFalse(processor.limitReached()); + + LinkDataMap linkDataMap2 = new LinkDataMap(); + linkDataMap2.addLinkData(new LinkData(application1, application1)); + linkDataMap2.addLinkData(new LinkData(application2, application2)); + LinkDataMap replaceLink = processor.processLinkDataMap(LinkDirection.IN_LINK, linkDataMap2, between); + Assertions.assertTrue(processor.limitReached()); + Assertions.assertEquals(1, replaceLink.size()); + + processor.processLinkDataMap(LinkDirection.IN_LINK, linkDataMap, between); + Assertions.assertTrue(processor.limitReached()); + } +} \ No newline at end of file