/* * Copyright 2014 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.filter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.navercorp.pinpoint.common.server.bo.SpanBo; import com.navercorp.pinpoint.common.server.bo.SpanEventBo; import com.navercorp.pinpoint.common.service.ServiceTypeRegistryService; import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.web.filter.agent.*; import com.navercorp.pinpoint.web.filter.responsetime.ResponseTimeFilter; import com.navercorp.pinpoint.web.filter.responsetime.ResponseTimeFilterFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; /** * * @author netspider * @author emeroad * */ public class LinkFilter implements Filter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final List<ServiceType> fromServiceDescList; private final String fromApplicationName; private final List<ServiceType> toServiceDescList; private final String toApplicationName; private final ResponseTimeFilter responseTimeFilter; private final ExecutionType executionType; private final FilterHint filterHint; private final AgentFilterFactory agentFilterFactory; private final AgentFilter fromAgentFilter; private final AgentFilter toAgentFilter; private final FilterType filterType; private final List<RpcHint> rpcHintList; private final URLPatternFilter acceptURLFilter; private final ServiceTypeRegistryService serviceTypeRegistryService; public LinkFilter(FilterDescriptor filterDescriptor, FilterHint filterHint, ServiceTypeRegistryService serviceTypeRegistryService) { if (filterDescriptor == null) { throw new NullPointerException("filter descriptor must not be null"); } if (filterHint == null) { throw new NullPointerException("filterHint must not be null"); } this.serviceTypeRegistryService = serviceTypeRegistryService; final String fromServiceType = filterDescriptor.getFromServiceType(); this.fromServiceDescList = serviceTypeRegistryService.findDesc(fromServiceType); if (this.fromServiceDescList == null) { throw new IllegalArgumentException("fromServiceDescList not found. fromServiceType:" + fromServiceType); } this.fromApplicationName = filterDescriptor.getFromApplicationName(); Assert.notNull(this.fromApplicationName, "fromApplicationName must not be null"); final String toServiceType = filterDescriptor.getToServiceType(); this.toServiceDescList = serviceTypeRegistryService.findDesc(toServiceType); if (toServiceDescList == null) { throw new IllegalArgumentException("toServiceDescList not found. toServiceDescList:" + toServiceType); } this.toApplicationName = filterDescriptor.getToApplicationName(); Assert.notNull(this.toApplicationName, "toApplicationName must not be null"); this.responseTimeFilter = createResponseTimeFilter(filterDescriptor); this.executionType = getExecutionType(filterDescriptor); this.filterHint = filterHint; Assert.notNull(this.filterHint, "filterHint must not be null"); final String fromAgentName = filterDescriptor.getFromAgentName(); final String toAgentName = filterDescriptor.getToAgentName(); this.agentFilterFactory = new AgentFilterFactory(fromAgentName, toAgentName); logger.debug("agentFilterFactory:{}", agentFilterFactory); this.fromAgentFilter = agentFilterFactory.createFromAgentFilter(); this.toAgentFilter = agentFilterFactory.createToAgentFilter(); this.filterType = getFilterType(); logger.info("filterType:{}", filterType); this.rpcHintList = this.filterHint.getRpcHintList(fromApplicationName); // TODO fix : fromSpan base rpccall filter this.acceptURLFilter = createPatternFilter(filterDescriptor); logger.info("acceptURLFilter:{}", acceptURLFilter); } private URLPatternFilter createPatternFilter(FilterDescriptor filterDescriptor) { if (filterDescriptor.getUrlPattern() == null) { return new BypassURLPatternFilter(); } // TODO remove decode return new AcceptUrlFilter(filterDescriptor.getUrlPattern()); } private ResponseTimeFilter createResponseTimeFilter(FilterDescriptor filterDescriptor) { final ResponseTimeFilterFactory factory = new ResponseTimeFilterFactory(filterDescriptor.getFromResponseTime(), filterDescriptor.getResponseTo()); return factory.createFilter(); } private ExecutionType getExecutionType(FilterDescriptor filterDescriptor) { final Boolean includeException = filterDescriptor.getIncludeException(); if (includeException == null) { return ExecutionType.ALL; } if (includeException) { return ExecutionType.FAIL_ONLY; } return ExecutionType.SUCCESS_ONLY; } enum FilterType { WAS_TO_WAS, USER_TO_WAS, WAS_TO_UNKNOWN, WAS_TO_BACKEND, WAS_TO_QUEUE, QUEUE_TO_WAS, UNSUPPORTED } enum ExecutionType { ALL, SUCCESS_ONLY, FAIL_ONLY } private FilterType getFilterType() { if (includeWas(fromServiceDescList) && includeWas(toServiceDescList)) { return FilterType.WAS_TO_WAS; } if (includeServiceType(fromServiceDescList, ServiceType.USER) && includeWas(toServiceDescList)) { return FilterType.USER_TO_WAS; } if (includeWas(fromServiceDescList) && includeUnknown(toServiceDescList)) { return FilterType.WAS_TO_UNKNOWN; } if (includeWas(fromServiceDescList) && includeQueue(toServiceDescList)) { return FilterType.WAS_TO_QUEUE; } if (includeQueue(fromServiceDescList) && includeWas(toServiceDescList)) { return FilterType.QUEUE_TO_WAS; } // TODO toServiceDescList check logic not exist. // if (includeWas(fromServiceDescList) && isBackEnd????()) { if (includeWas(fromServiceDescList)) { return FilterType.WAS_TO_BACKEND; } return FilterType.UNSUPPORTED; } private boolean checkResponseCondition(long elapsed, boolean hasError) { if (responseTimeFilter.accept(elapsed) == ResponseTimeFilter.REJECT) { return false; } switch (executionType) { case ALL: { return true; } case FAIL_ONLY: { // is error if (hasError == true) { return true; } return false; } case SUCCESS_ONLY: { // is success if (hasError == false) { return true; } return false; } default: { throw new UnsupportedOperationException("Unsupported ExecutionType:" + executionType); } } } @Override public boolean include(List<SpanBo> transaction) { switch (this.filterType) { case USER_TO_WAS: { return userToWasFilter(transaction); } case WAS_TO_UNKNOWN: { return wasToUnknownFilter(transaction); } case WAS_TO_WAS: { return wasToWasFilter(transaction); } case WAS_TO_QUEUE: { return wasToQueueFilter(transaction); } case QUEUE_TO_WAS: { return queueToWasFilter(transaction); } case WAS_TO_BACKEND: { return wasToBackendFilter(transaction); } default: { logger.warn("unsupported filter type:{}", this); throw new IllegalArgumentException("unsupported filter type:"); } } } /** * USER -> WAS */ private boolean userToWasFilter(List<SpanBo> transaction) { final List<SpanBo> toNode = findToNode(transaction); for (SpanBo span : toNode) { if (span.isRoot()) { if (checkResponseCondition(span.getElapsed(), isError(span))) { return true; } } } return false; } private boolean isError(SpanBo span) { return span.getErrCode() > 0; } private boolean wasToUnknownFilter(List<SpanBo> transaction) { /* * WAS -> UNKNOWN */ final List<SpanBo> fromNode = findFromNode(transaction); for (SpanBo span : fromNode) { final List<SpanEventBo> eventBoList = span.getSpanEventBoList(); if (eventBoList == null) { continue; } for (SpanEventBo event : eventBoList) { // check only whether a client exists or not. final ServiceType eventServiceType = serviceTypeRegistryService.findServiceType(event.getServiceType()); if (eventServiceType.isRpcClient() && eventServiceType.isRecordStatistics()) { if (toApplicationName.equals(event.getDestinationId())) { if (checkResponseCondition(event.getEndElapsed(), event.hasException())) { return true; } } } } } return false; } /** * WAS -> BACKEND (non-WAS) */ private boolean wasToBackendFilter(List<SpanBo> transaction) { final List<SpanBo> fromNode = findFromNode(transaction); for (SpanBo span : fromNode) { final List<SpanEventBo> eventBoList = span.getSpanEventBoList(); if (eventBoList == null) { continue; } for (SpanEventBo event : eventBoList) { final ServiceType eventServiceType = serviceTypeRegistryService.findServiceType(event.getServiceType()); if (isToNode(event.getDestinationId(), eventServiceType)) { if (checkResponseCondition(event.getEndElapsed(), event.hasException())) { return true; } } } } return false; } /** * WAS -> WAS */ private boolean wasToWasFilter(List<SpanBo> transaction) { /* * WAS -> WAS * if destination is a "WAS", the span of src and dest may exists. need to check if be circular or not. * find src first. span (from, to) may exist more than one. so (spanId == parentSpanID) should be checked. */ final List<SpanBo> fromSpanList = findFromNode(transaction); if (fromSpanList.isEmpty()) { // from span not found return false; } final List<SpanBo> toSpanList = findToNode(transaction); if (!toSpanList.isEmpty()) { // from -> to compare SpanId & pSpanId filter final boolean exactMatch = wasToWasExactMatch(fromSpanList, toSpanList); if (exactMatch) { return true; } } if (isToAgentFilter()) { // fast skip. toAgent filtering condition exist. // url filter not available. return false; } if (!rpcHintList.isEmpty()) { return false; } // if agent filter is FromAgentFilter or AcceptAgentFilter(agent filter is not selected), url filtering is available. return fromBaseFilter(fromSpanList); } /** * WAS -> Queue (virtual) * Should be the same as {@link #wasToBackendFilter} */ private boolean wasToQueueFilter(List<SpanBo> transaction) { return wasToBackendFilter(transaction); } /** * Queue (virtual) -> WAS */ private boolean queueToWasFilter(List<SpanBo> transaction) { final List<SpanBo> toNode = findToNode(transaction); logger.debug("matching toNode spans: {}", toNode); for (SpanBo span : toNode) { if (fromApplicationName.equals(span.getAcceptorHost())) { if (checkResponseCondition(span.getElapsed(), isError(span))) { return true; } } } return false; } private boolean fromBaseFilter(List<SpanBo> fromSpanList) { // from base filter. hint base filter // exceptional case // 1. remote call fail // 2. span packet lost. for (SpanBo fromSpan : fromSpanList) { final List<SpanEventBo> eventBoList = fromSpan.getSpanEventBoList(); if (eventBoList == null) { continue; } for (SpanEventBo event : eventBoList) { final ServiceType eventServiceType = serviceTypeRegistryService.findServiceType(event.getServiceType()); if (!eventServiceType.isRpcClient() || !eventServiceType.isQueue()) { continue; } if (!eventServiceType.isRecordStatistics()) { continue; } // check rpc call fail for (RpcHint rpcHint : rpcHintList) { for (RpcType rpcType : rpcHint.getRpcTypeList()) { if (rpcType.isMatched(event.getDestinationId(), eventServiceType.getCode())) { if (checkResponseCondition(event.getEndElapsed(), event.hasException())) { return true; } } } } } } return false; } private boolean isToAgentFilter() { return this.agentFilterFactory.toAgentExist(); } private boolean wasToWasExactMatch(List<SpanBo> fromSpanList, List<SpanBo> toSpanList) { // from -> to compare SpanId & pSpanId filter for (SpanBo fromSpanBo : fromSpanList) { for (SpanBo toSpanBo : toSpanList) { if (fromSpanBo == toSpanBo) { // skip same object; continue; } if (fromSpanBo.getSpanId() == toSpanBo.getParentSpanId()) { final int elapsed = toSpanBo.getElapsed(); final boolean error = isError(toSpanBo); if (checkResponseCondition(elapsed, error)) { return true; } } } } return false; } private List<SpanBo> findFromNode(List<SpanBo> transaction) { final List<SpanBo> node = findNode(transaction, fromApplicationName, fromServiceDescList, fromAgentFilter); // RpcURLPatternFilter rpcURLPatternFilter = new RpcURLPatternFilter("/**/*"); // if (!rpcURLPatternFilter.accept(node)) { // return Collections.emptyList(); // } return node; } private List<SpanBo> findToNode(List<SpanBo> transaction) { final List<SpanBo> node = findNode(transaction, toApplicationName, toServiceDescList, toAgentFilter); if (!acceptURLFilter.accept(node)) { return Collections.emptyList(); } return node; } private List<SpanBo> findNode(List<SpanBo> nodeList, String findApplicationName, List<ServiceType> findServiceCode, AgentFilter agentFilter) { List<SpanBo> findList = null; for (SpanBo span : nodeList) { final ServiceType applicationServiceType = serviceTypeRegistryService.findServiceType(span.getApplicationServiceType()); if (findApplicationName.equals(span.getApplicationId()) && includeServiceType(findServiceCode, applicationServiceType)) { // apply preAgentFilter if (agentFilter.accept(span.getAgentId())) { if (findList == null) { findList = new ArrayList<>(); } findList.add(span); } } } if (findList == null) { return Collections.emptyList(); } return findList; } private boolean isToNode(String applicationId, ServiceType serviceType) { return this.toApplicationName.equals(applicationId) && includeServiceType(this.toServiceDescList, serviceType); } private boolean includeUnknown(List<ServiceType> serviceTypeList) { for (ServiceType serviceType : serviceTypeList) { if (serviceType.isUnknown()) { return true; } } return false; } private boolean includeWas(List<ServiceType> serviceTypeList) { for (ServiceType serviceType : serviceTypeList) { if (serviceType.isWas()) { return true; } } return false; } private boolean includeQueue(List<ServiceType> serviceTypeList) { for (ServiceType serviceType : serviceTypeList) { if (serviceType.isQueue()) { return true; } } return false; } private boolean includeServiceType(List<ServiceType> serviceTypeList, ServiceType targetServiceType) { for (ServiceType serviceType : serviceTypeList) { if (serviceType == targetServiceType) { return true; } } return false; } @Override public String toString() { final StringBuilder sb = new StringBuilder("LinkFilter{"); sb.append("fromServiceDescList=").append(fromServiceDescList); sb.append(", fromApplicationName='").append(fromApplicationName).append('\''); sb.append(", toServiceDescList=").append(toServiceDescList); sb.append(", toApplicationName='").append(toApplicationName).append('\''); sb.append(", responseTimeFilter=").append(responseTimeFilter); sb.append(", executionType=").append(executionType); sb.append(", filterHint=").append(filterHint); sb.append(", agentFilterFactory=").append(agentFilterFactory); sb.append(", fromAgentFilter=").append(fromAgentFilter); sb.append(", toAgentFilter=").append(toAgentFilter); sb.append(", filterType=").append(filterType); sb.append(", rpcHintList=").append(rpcHintList); sb.append(", acceptURLFilter=").append(acceptURLFilter); sb.append('}'); return sb.toString(); } }