Skip to content

Commit

Permalink
[pinpoint-apm#10640] Add node limit in ServerMap to prevent OOM
Browse files Browse the repository at this point in the history
  • Loading branch information
emeroad committed Jan 24, 2024
1 parent ef2d01b commit c2a43b5
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 = {
Expand Down Expand Up @@ -80,12 +84,18 @@ public ApplicationsMapCreatorFactory applicationsMapCreatorFactory(@Qualifier("a
return new ApplicationsMapCreatorFactory(executor);
}

@Bean
public Supplier<LinkDataMapProcessor> 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> serverMapDataFilter) {
return new LinkSelectorFactory(linkDataMapService, applicationsMapCreatorFactory, hostApplicationMapDao, serverMapDataFilter);
Optional<ServerMapDataFilter> serverMapDataFilter,
Supplier<LinkDataMapProcessor> applicationLimiterProcessorFactory) {
return new LinkSelectorFactory(linkDataMapService, applicationsMapCreatorFactory, hostApplicationMapDao, serverMapDataFilter, applicationLimiterProcessorFactory);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

/**
* @author HyunGil Jeong
Expand All @@ -41,26 +42,31 @@ public class LinkSelectorFactory {
private final HostApplicationMapDao hostApplicationMapDao;

private final ServerMapDataFilter serverMapDataFilter;
private final Supplier<LinkDataMapProcessor> applicationLimiterProcessorFactory;

public LinkSelectorFactory(
LinkDataMapService linkDataMapService,
ApplicationsMapCreatorFactory applicationsMapCreatorFactory,
HostApplicationMapDao hostApplicationMapDao,
Optional<ServerMapDataFilter> serverMapDataFilter) {
Optional<ServerMapDataFilter> serverMapDataFilter,
Supplier<LinkDataMapProcessor> 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) {
return createLinkSelector(linkSelectorType, LinkDataMapProcessor.NO_OP, LinkDataMapProcessor.NO_OP);
}

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);
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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 int counter = 0;


public ApplicationLimiterProcessor(int maxLinkSize) {
this.maxLinkSize = maxLinkSize;
}

boolean limitReached() {
synchronized (this) {
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<LinkData> 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;
}
}

}
Original file line number Diff line number Diff line change
@@ -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<LinkDataMapProcessor> {

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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit c2a43b5

Please sign in to comment.