/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.alipay.lookout.remote.report.poller; import com.alipay.lookout.api.Gauge; import com.alipay.lookout.api.Metric; import com.alipay.lookout.common.log.LookoutLoggerFactory; import com.alipay.lookout.common.top.RollableTopGauge; import com.alipay.lookout.common.utils.CommonUtil; import com.alipay.lookout.core.GaugeWrapper; import com.alipay.lookout.core.InfoWrapper; import com.alipay.lookout.core.config.LookoutConfig; import com.alipay.lookout.core.config.MetricConfig; import com.alipay.lookout.remote.model.LookoutMeasurement; import com.alipay.lookout.remote.step.LookoutRegistry; import com.alipay.lookout.remote.step.PollableInfoWrapper; import com.google.common.base.Preconditions; import com.google.common.primitives.Longs; import org.slf4j.Logger; import java.io.Closeable; import java.util.*; import java.util.concurrent.*; /** * @author xiangfeng.xzc * @since 2018/7/26 */ public class PollerController implements Closeable { private static final Logger LOGGER = LookoutLoggerFactory .getLogger(PollerController.class); private static final int DEFAULT_SLOT_COUNT = 3; private static final int DEFAULT_IDLE_SECONDS = 1800; //30 min /** * 比较器 按照cursor倒序排序 */ private static final Comparator<Slot> COMPARATOR = new Comparator<Slot>() { @Override public int compare(Slot o1, Slot o2) { return Longs.compare( o2.getCursor(), o1.getCursor()); } }; /** * 注册中心 */ private final LookoutRegistry registry; /** * 只有1个线程的调度器 */ private ScheduledExecutorService scheduledExecutorService; /** * 采样间隔时间, step将会扩散到registry包含的所有实现了 ResettableStep 接口的 metric */ private volatile long step = -1; /** * 槽数量 */ private volatile int slotCount; /** * 缓存 */ private volatile MetricCache metricCache; /** * 是否处于激活状态(最近有poll过数据) */ private boolean active = false; /** * 定时poll数据的task的future */ private ScheduledFuture<?> idleFuture; /** * 监听器 */ private final List<Listener> listeners = new CopyOnWriteArrayList<Listener>(); /** * 空闲检测定时的future */ private ScheduledFuture<?> pollerFuture; /** * 多少时间没有请求就算是空闲 */ private int idleSeconds; public PollerController(LookoutRegistry registry) { this(registry, DEFAULT_SLOT_COUNT); } public PollerController(LookoutRegistry registry, int initSlotCount) { this.registry = registry; scheduledExecutorService = new ScheduledThreadPoolExecutor(1, CommonUtil.getNamedThreadFactory("poller-controller"), new ThreadPoolExecutor.AbortPolicy()); idleSeconds = registry.getConfig().getInteger(LookoutConfig.LOOKOUT_EXPORTER_IDLE_SECONDS, DEFAULT_IDLE_SECONDS); update(registry.getCurrentStepMillis(), initSlotCount); } MetricConfig getMetricConfig() { return registry.getConfig(); } /** * 因为step或slowCount的调整, 导致需要重建 MetricCache, 这个方法尽量保留已有的slot, 减少数据丢失 * * @param step step * @param slotCount slotCount * @return */ private MetricCache createCache(long step, int slotCount) { MetricCache oldCache = this.metricCache; if (oldCache != null) { return new MetricCache(oldCache, step, slotCount); } else { return new MetricCache(registry.clock(), step, slotCount); } } /** * 修改slotCount * * @param slotCount slotCount */ public void setSlotCount(int slotCount) { Preconditions.checkArgument(slotCount > 0, "slotCount must greater than 0"); if (this.slotCount == slotCount) { return; } synchronized (this) { this.slotCount = slotCount; if (this.step <= 0) { return; } this.metricCache = createCache(step, slotCount); } } /** * 修改频率 * * @param step step */ public void setStep(long step) { if (this.step == step) { return; } synchronized (this) { this.step = step; if (this.pollerFuture != null) { pollerFuture.cancel(true); } this.pollerFuture = null; // 认为step<=0则无需启动任务 if (step <= 0) { this.metricCache = null; return; } this.metricCache = createCache(step, slotCount); Runnable runnable = new Runnable() { @Override public void run() { MetricCache cache = PollerController.this.metricCache; if (!active || cache == null) { return; } List<MetricDto> result = getMetricDtos(); cache.add(result); } }; pollerFuture = scheduledExecutorService.scheduleAtFixedRate(runnable, 0, step, TimeUnit.MILLISECONDS); } } /** * 获得下一批是数据 * * @param successCursors 上一次成功的 cursors * @return next data */ public List<Slot> getNextData(Set<Long> successCursors) { touchTimer(); MetricCache array = this.metricCache; if (array == null) { return Collections.emptyList(); } else { List<Slot> data = array.getNextData(successCursors); Collections.sort(data, COMPARATOR); return data; } } public void clear() { MetricCache array = this.metricCache; if (array != null) { array.clear(); } } public void destroy() { scheduledExecutorService.shutdownNow(); } /** * 更新该 poller 的配置 * * @param newStep newStep * @param newSlotCount newSlotCount */ public synchronized void update(long newStep, int newSlotCount) { Preconditions.checkArgument(newStep >= 0, "step must greater than 0"); setSlotCount(newSlotCount); setStep(newStep); registry.setStep(newStep); } /** * 获取槽数量 * * @return slot count */ public int getSlotCount() { return slotCount; } /** * 获取采样步长 * * @return step */ public long getStep() { return step; } private List<MetricDto> getMetricDtos() { List<MetricDto> results = new ArrayList<MetricDto>(); long polledTime = System.currentTimeMillis(); Iterator<Metric> it = registry.iterator(); while (it.hasNext()) { Metric metric = it.next(); if (metric instanceof GaugeWrapper) { Gauge gauge = ((GaugeWrapper) metric).getOriginalOne(); if (gauge instanceof RollableTopGauge) { ((RollableTopGauge) gauge).roll(polledTime); it.remove(); } } if (metric instanceof InfoWrapper) { //ignore info if (registry.getConfig().getBoolean( LookoutConfig.LOOKOUT_AUTOPOLL_INFO_METRIC_IGNORE, true)) { continue; } if (!((PollableInfoWrapper) metric).isAutoPolledAllowed(getStep())) { continue; } } MetricDto dto = new MetricDto(); LookoutMeasurement lookoutMeasurement = LookoutMeasurement.from(metric, registry); dto.setName(lookoutMeasurement.metricId().name()); dto.setTimestamp(lookoutMeasurement.getDate().getTime()); dto.setMetrics(lookoutMeasurement.getValues()); dto.setTags(lookoutMeasurement.getTags()); results.add(dto); } return results; } /** * 刷新一下定时器 */ private synchronized void touchTimer() { // 说明从 idle 状态 转成 active 状态 if (!active) { active = true; triggerActive(); } // 取消旧的计时器 ScheduledFuture<?> oldFuture = this.idleFuture; if (oldFuture != null && !oldFuture.isDone()) { oldFuture.cancel(true); } // 空闲时间到了,进入idle状态 this.idleFuture = scheduledExecutorService.schedule(new Runnable() { @Override public void run() { synchronized (PollerController.this) { active = false; triggerIdle(); } } }, idleSeconds, TimeUnit.SECONDS); } private void triggerIdle() { //fallback to proactive registry.setProactive(true); // idle时不再poller 同时也清理掉cache this.metricCache.clear(); LOGGER.warn("PollerController is now idle. reactive mode."); for (Listener listener : listeners) { listener.onIdle(); } } private void triggerActive() { LOGGER.warn("PollerController is now active. proactive mode."); //reactive mode. registry.setProactive(false); for (Listener listener : listeners) { listener.onActive(); } } /** * 添加监听器 * * @param listener listener */ public void addListener(Listener listener) { this.listeners.add(listener); } /** * 删除监听器 * * @param listener listener */ public void removeListener(Listener listener) { this.listeners.remove(listener); } @Override public void close() { if (this.scheduledExecutorService != null) { this.scheduledExecutorService.shutdown(); this.scheduledExecutorService = null; } registry.destroy(); } }