/* * Copyright 2017 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.vo.timeline.inspector; import com.navercorp.pinpoint.common.server.util.AgentEventType; import com.navercorp.pinpoint.common.server.util.AgentEventTypeCategory; import com.navercorp.pinpoint.web.filter.agent.AgentEventFilter; import com.navercorp.pinpoint.web.vo.AgentEvent; import com.navercorp.pinpoint.web.vo.AgentStatus; import com.navercorp.pinpoint.web.vo.Range; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Queue; /** * @author HyunGil Jeong */ public class AgentStatusTimelineBuilder { private static final AgentEventFilter LIFECYCLE_EVENT_FILTER = new AgentEventFilter() { @Override public boolean accept(AgentEvent agentEvent) { AgentEventType agentEventType = AgentEventType.getTypeByCode(agentEvent.getEventTypeCode()); if (agentEventType == null) { return REJECT; } if (agentEventType.isCategorizedAs(AgentEventTypeCategory.AGENT_LIFECYCLE)) { return ACCEPT; } return REJECT; } }; private final long timelineStartTimestamp; private final long timelineEndTimestamp; private final AgentState initialState; private List<AgentEvent> agentEvents; private boolean hasOverlap = false; public AgentStatusTimelineBuilder(Range range, AgentStatus initialStatus) { Assert.notNull(range, "range must not be null"); Assert.isTrue(range.getRange() > 0, "timeline must have range greater than 0"); timelineStartTimestamp = range.getFrom(); timelineEndTimestamp = range.getTo(); if (initialStatus == null) { initialState = AgentState.UNKNOWN; } else { initialState = AgentState.fromAgentLifeCycleState(initialStatus.getState()); } } public AgentStatusTimelineBuilder from(List<AgentEvent> agentEvents) { this.agentEvents = agentEvents; return this; } public AgentStatusTimeline build() { if (agentEvents == null) { agentEvents = Collections.emptyList(); } else { agentEvents = Collections.unmodifiableList(agentEvents); } List<AgentStatusTimelineSegment> timelineSegments = createTimelineSegments(agentEvents); return new AgentStatusTimeline(timelineSegments, hasOverlap); } private List<AgentEvent> filterAgentEvents(AgentEventFilter agentEventFilter, List<AgentEvent> agentEvents) { List<AgentEvent> filteredEvents = new ArrayList<>(); for (AgentEvent agentEvent : agentEvents) { if (agentEventFilter.accept(agentEvent)) { filteredEvents.add(agentEvent); } } return filteredEvents; } private List<AgentStatusTimelineSegment> createTimelineSegments(List<AgentEvent> agentEvents) { if (CollectionUtils.isEmpty(agentEvents)) { AgentStatusTimelineSegment segment = createSegment(timelineStartTimestamp, timelineEndTimestamp, initialState); return Collections.singletonList(segment); } else { List<AgentEvent> lifeCycleEvents = filterAgentEvents(LIFECYCLE_EVENT_FILTER, agentEvents); List<AgentLifeCycle> agentLifeCycles = createAgentLifeCycles(lifeCycleEvents); return convertToTimelineSegments(agentLifeCycles); } } private List<AgentLifeCycle> createAgentLifeCycles(List<AgentEvent> agentEvents) { Map<Long, List<AgentEvent>> partitions = partitionByStartTimestamp(agentEvents); List<AgentLifeCycle> agentLifeCycles = new ArrayList<>(partitions.size()); for (Map.Entry<Long, List<AgentEvent>> e : partitions.entrySet()) { Long agentStartTimestamp = e.getKey(); List<AgentEvent> agentLifeCycleEvents = e.getValue(); agentLifeCycles.add(createAgentLifeCycle(agentStartTimestamp, agentLifeCycleEvents)); } return mergeOverlappingLifeCycles(agentLifeCycles); } private Map<Long, List<AgentEvent>> partitionByStartTimestamp(List<AgentEvent> agentEvents) { Map<Long, List<AgentEvent>> partitions = new HashMap<>(); for (AgentEvent agentEvent : agentEvents) { long startTimestamp = agentEvent.getStartTimestamp(); List<AgentEvent> partition = partitions.get(startTimestamp); if (partition == null) { partition = new ArrayList<>(); partitions.put(startTimestamp, partition); } partition.add(agentEvent); } return partitions; } private AgentLifeCycle createAgentLifeCycle(long agentStartTimestamp, List<AgentEvent> agentEvents) { Collections.sort(agentEvents, AgentEvent.EVENT_TIMESTAMP_ASC_COMPARATOR); AgentEvent first = agentEvents.get(0); AgentEvent last = agentEvents.get(agentEvents.size() - 1); AgentState endState = AgentState.fromAgentEvent(last); long startTimestamp = first.getStartTimestamp(); if (agentStartTimestamp <= timelineStartTimestamp) { startTimestamp = timelineStartTimestamp; } long endTimestamp = last.getEventTimestamp(); if (endState == AgentState.RUNNING) { endTimestamp = timelineEndTimestamp; } return new AgentLifeCycle(startTimestamp, endTimestamp, endState); } private List<AgentLifeCycle> mergeOverlappingLifeCycles(List<AgentLifeCycle> agentLifeCycles) { Collections.sort(agentLifeCycles, AgentLifeCycle.START_TIMESTAMP_ASC_COMPARATOR); Queue<AgentLifeCycle> mergedAgentLifeCycles = new PriorityQueue<>(agentLifeCycles.size(), AgentLifeCycle.START_TIMESTAMP_ASC_COMPARATOR); for (AgentLifeCycle agentLifeCycle : agentLifeCycles) { Iterator<AgentLifeCycle> mergedAgentLifeCyclesIterator = mergedAgentLifeCycles.iterator(); while (mergedAgentLifeCyclesIterator.hasNext()) { AgentLifeCycle mergedAgentLifeCycle = mergedAgentLifeCyclesIterator.next(); if (mergedAgentLifeCycle.isOverlapping(agentLifeCycle)) { mergedAgentLifeCyclesIterator.remove(); agentLifeCycle = AgentLifeCycle.merge(agentLifeCycle, mergedAgentLifeCycle); hasOverlap = true; } } mergedAgentLifeCycles.add(agentLifeCycle); } return new ArrayList<>(mergedAgentLifeCycles); } private List<AgentStatusTimelineSegment> convertToTimelineSegments(List<AgentLifeCycle> agentLifeCycles) { List<AgentStatusTimelineSegment> segments = new ArrayList<>(); AgentStatusTimelineSegment fillerSegment = null; for (AgentLifeCycle agentLifeCycle : agentLifeCycles) { if (fillerSegment != null) { fillerSegment.setEndTimestamp(agentLifeCycle.getStartTimestamp()); segments.add(fillerSegment); } else if (agentLifeCycle.getStartTimestamp() > timelineStartTimestamp) { if (initialState == AgentState.RUNNING) { hasOverlap = true; } fillerSegment = initializeFillerSegment(timelineStartTimestamp, initialState); fillerSegment.setEndTimestamp(agentLifeCycle.getStartTimestamp()); segments.add(fillerSegment); } AgentStatusTimelineSegment lifeCycleSegment = agentLifeCycle.toTimelineSegment(); segments.add(lifeCycleSegment); fillerSegment = initializeFillerSegment(agentLifeCycle.getEndTimestamp(), agentLifeCycle.getEndState()); } if (fillerSegment != null && fillerSegment.getStartTimestamp() < timelineEndTimestamp) { fillerSegment.setEndTimestamp(timelineEndTimestamp); segments.add(fillerSegment); } return segments; } private AgentStatusTimelineSegment initializeFillerSegment(long startTimestamp, AgentState state) { AgentStatusTimelineSegment fillerSegment = new AgentStatusTimelineSegment(); fillerSegment.setStartTimestamp(startTimestamp); fillerSegment.setValue(state); return fillerSegment; } private AgentStatusTimelineSegment createSegment(long startTimestamp, long endTimestamp, AgentState state) { AgentStatusTimelineSegment segment = new AgentStatusTimelineSegment(); segment.setStartTimestamp(startTimestamp); segment.setEndTimestamp(endTimestamp); segment.setValue(state); return segment; } private static class AgentLifeCycle { private static final Comparator<AgentLifeCycle> START_TIMESTAMP_ASC_COMPARATOR = new Comparator<AgentLifeCycle>() { @Override public int compare(AgentLifeCycle o1, AgentLifeCycle o2) { return Long.compare(o1.getStartTimestamp(), o2.getStartTimestamp()); } }; private final long startTimestamp; private final long endTimestamp; private final AgentState endState; private AgentLifeCycle(long startTimestamp, long endTimestamp, AgentState endState) { if (startTimestamp >= endTimestamp) { throw new IllegalArgumentException("startTimestamp must be less than endTimestamp"); } this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; this.endState = endState; } private long getStartTimestamp() { return startTimestamp; } private long getEndTimestamp() { return endTimestamp; } private AgentState getEndState() { return endState; } private AgentStatusTimelineSegment toTimelineSegment() { AgentStatusTimelineSegment timelineSegment = new AgentStatusTimelineSegment(); timelineSegment.setStartTimestamp(startTimestamp); timelineSegment.setEndTimestamp(endTimestamp); timelineSegment.setValue(AgentState.RUNNING); return timelineSegment; } private boolean isOverlapping(AgentLifeCycle other) { if (this.startTimestamp < other.getStartTimestamp()) { return other.getStartTimestamp() <= this.endTimestamp; } else if (this.startTimestamp > other.getStartTimestamp()) { return this.startTimestamp <= other.getEndTimestamp(); } else { return true; } } @Override public String toString() { final StringBuilder sb = new StringBuilder("AgentLifeCycle{"); sb.append("startTimestamp=").append(startTimestamp); sb.append(", endTimestamp=").append(endTimestamp); sb.append(", endState=").append(endState); sb.append('}'); return sb.toString(); } private static AgentLifeCycle merge(AgentLifeCycle o1, AgentLifeCycle o2) { long newStartTimestamp = Math.min(o1.getStartTimestamp(), o2.getStartTimestamp()); if (o1.getEndTimestamp() > o2.getEndTimestamp()) { return new AgentLifeCycle(newStartTimestamp, o1.getEndTimestamp(), o1.getEndState()); } else { return new AgentLifeCycle(newStartTimestamp, o2.getEndTimestamp(), o2.getEndState()); } } } }