/*
* 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.mapper.stat.sampling.sampler;
import com.navercorp.pinpoint.common.server.bo.JvmGcType;
import com.navercorp.pinpoint.common.server.bo.stat.JvmGcBo;
import com.navercorp.pinpoint.web.vo.chart.Point;
import com.navercorp.pinpoint.web.vo.chart.UncollectedPoint;
import com.navercorp.pinpoint.web.vo.stat.chart.DownSampler;
import com.navercorp.pinpoint.web.vo.stat.chart.DownSamplers;
import com.navercorp.pinpoint.web.vo.stat.SampledJvmGc;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author HyunGil Jeong
*/
@Component
public class JvmGcSampler implements AgentStatSampler<JvmGcBo, SampledJvmGc> {
public static final DownSampler<Long> LONG_DOWN_SAMPLER = DownSamplers.getLongDownSampler(JvmGcBo.UNCOLLECTED_VALUE);
@Override
public SampledJvmGc sampleDataPoints(int timeWindowIndex, long timestamp, List<JvmGcBo> dataPoints, JvmGcBo previousDataPoint) {
JvmGcType jvmGcType = JvmGcType.UNKNOWN;
List<Long> heapUseds = new ArrayList<>(dataPoints.size());
List<Long> heapMaxes = new ArrayList<>(dataPoints.size());
List<Long> nonHeapUseds = new ArrayList<>(dataPoints.size());
List<Long> nonHeapMaxes = new ArrayList<>(dataPoints.size());
List<Long> gcOldCounts = new ArrayList<>(dataPoints.size());
List<Long> gcOldTimes = new ArrayList<>(dataPoints.size());
// dataPoints are in descending order
JvmGcBo previousBo = previousDataPoint;
for (int i = dataPoints.size() - 1; i >= 0; --i) {
JvmGcBo jvmGcBo = dataPoints.get(i);
jvmGcType = jvmGcBo.getGcType();
if (jvmGcBo.getHeapUsed() != JvmGcBo.UNCOLLECTED_VALUE) {
heapUseds.add(jvmGcBo.getHeapUsed());
}
if (jvmGcBo.getHeapMax() != JvmGcBo.UNCOLLECTED_VALUE) {
heapMaxes.add(jvmGcBo.getHeapMax());
}
if (jvmGcBo.getNonHeapUsed() != JvmGcBo.UNCOLLECTED_VALUE) {
nonHeapUseds.add(jvmGcBo.getNonHeapUsed());
}
if (jvmGcBo.getNonHeapMax() != JvmGcBo.UNCOLLECTED_VALUE) {
nonHeapMaxes.add(jvmGcBo.getNonHeapMax());
}
if (previousBo != null) {
// Technically, this should not be needed as data should already be partitioned by their agent start
// timestamp and should only contain data from a single jvm life cycle.
// Added to maintain backwards compatibility for data that do not have agent start timestamp.
if (checkJvmRestart(previousBo, jvmGcBo)) {
if (isGcCollected(jvmGcBo)) {
gcOldCounts.add(jvmGcBo.getGcOldCount());
gcOldTimes.add(jvmGcBo.getGcOldTime());
} else {
jvmGcBo.setGcOldCount(0L);
jvmGcBo.setGcOldTime(0L);
}
} else {
if (isGcCollected(jvmGcBo) && isGcCollected(previousBo)) {
gcOldCounts.add(jvmGcBo.getGcOldCount() - previousBo.getGcOldCount());
gcOldTimes.add(jvmGcBo.getGcOldTime() - previousBo.getGcOldTime());
} else {
if (!isGcCollected(jvmGcBo)) {
jvmGcBo.setGcOldCount(previousBo.getGcOldCount());
jvmGcBo.setGcOldTime(previousBo.getGcOldTime());
}
}
}
} else {
if (isGcCollected(jvmGcBo)) {
if (timeWindowIndex > 0) {
gcOldCounts.add(jvmGcBo.getGcOldCount());
gcOldTimes.add(jvmGcBo.getGcOldTime());
} else {
gcOldCounts.add(0L);
gcOldTimes.add(0L);
}
}
}
previousBo = jvmGcBo;
}
SampledJvmGc sampledJvmGc = new SampledJvmGc();
sampledJvmGc.setJvmGcType(jvmGcType);
sampledJvmGc.setHeapUsed(createSampledPoint(timestamp, heapUseds));
sampledJvmGc.setHeapMax(createSampledPoint(timestamp, heapMaxes));
sampledJvmGc.setNonHeapUsed(createSampledPoint(timestamp, nonHeapUseds));
sampledJvmGc.setNonHeapMax(createSampledPoint(timestamp, nonHeapMaxes));
sampledJvmGc.setGcOldCount(createSampledPoint(timestamp, gcOldCounts));
sampledJvmGc.setGcOldTime(createSampledPoint(timestamp, gcOldTimes));
return sampledJvmGc;
}
private boolean isGcCollected(JvmGcBo jvmGcBo) {
return jvmGcBo.getGcOldCount() != JvmGcBo.UNCOLLECTED_VALUE && jvmGcBo.getGcOldTime() != JvmGcBo.UNCOLLECTED_VALUE;
}
private boolean checkJvmRestart(JvmGcBo previous, JvmGcBo current) {
if (previous.getStartTimestamp() > 0 && current.getStartTimestamp() > 0) {
return previous.getStartTimestamp() != current.getStartTimestamp();
} else {
// if start timestamp is not serialzied
if (current.getGcOldTime() == JvmGcBo.UNCOLLECTED_VALUE || current.getGcOldCount() == JvmGcBo.UNCOLLECTED_VALUE) {
return false;
} else {
long countDelta = current.getGcOldCount() - previous.getGcOldCount();
long timeDelta = current.getGcOldTime() - previous.getGcOldTime();
return countDelta < 0 && timeDelta < 0;
}
}
}
private Point<Long, Long> createSampledPoint(long timestamp, List<Long> values) {
if (values.isEmpty()) {
return new UncollectedPoint<>(timestamp, JvmGcBo.UNCOLLECTED_VALUE);
} else {
return new Point<>(
timestamp,
LONG_DOWN_SAMPLER.sampleMin(values),
LONG_DOWN_SAMPLER.sampleMax(values),
LONG_DOWN_SAMPLER.sampleAvg(values, 0),
LONG_DOWN_SAMPLER.sampleSum(values));
}
}
}