/** * 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.alibaba.jstorm.daemon.supervisor; import java.io.IOException; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import backtype.storm.utils.LocalState; import backtype.storm.utils.Utils; import com.alibaba.jstorm.client.ConfigExtension; import com.alibaba.jstorm.cluster.Common; import com.alibaba.jstorm.daemon.worker.LocalAssignment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import backtype.storm.Config; import com.alibaba.jstorm.callback.RunnableCallback; import com.alibaba.jstorm.cluster.StormClusterState; import com.alibaba.jstorm.cluster.StormConfig; import com.alibaba.jstorm.utils.JStormServerUtils; import com.alibaba.jstorm.utils.JStormUtils; import com.alibaba.jstorm.utils.TimeUtils; /** * supervisor heartbeat, just write SupervisorInfo to ZK * * @author Johnfang (xiaojian.fxj@alibaba-inc.com) */ class Heartbeat extends RunnableCallback { private static final Logger LOG = LoggerFactory.getLogger(Heartbeat.class); private Map<Object, Object> conf; private StormClusterState stormClusterState; private String supervisorId; private String myHostName; private final int startTime; private final int frequency; private SupervisorInfo supervisorInfo; private AtomicBoolean hbUpdateTrigger; protected volatile HealthStatus healthStatus; private LocalState localState; @SuppressWarnings({"rawtypes", "unchecked"}) public Heartbeat(Map conf, StormClusterState stormClusterState, String supervisorId, LocalState localState) { String myHostName = JStormServerUtils.getHostName(conf); this.stormClusterState = stormClusterState; this.supervisorId = supervisorId; this.conf = conf; this.myHostName = myHostName; this.startTime = TimeUtils.current_time_secs(); this.frequency = JStormUtils.parseInt(conf.get(Config.SUPERVISOR_HEARTBEAT_FREQUENCY_SECS)); this.hbUpdateTrigger = new AtomicBoolean(true); this.localState = localState; this.healthStatus = HealthStatus.INFO; initSupervisorInfo(conf); LOG.info("Successfully inited supervisor heartbeat thread, " + supervisorInfo); } private void initSupervisorInfo(Map conf) { Set<Integer> portList = JStormUtils.getDefaultSupervisorPortList(conf); if (!StormConfig.local_mode(conf)) { try { boolean isLocalIP = myHostName.equals("127.0.0.1") || myHostName.equals("localhost"); if (isLocalIP) { throw new Exception("the hostname supervisor got is localhost"); } } catch (Exception e1) { LOG.error("get supervisor host error!", e1); throw new RuntimeException(e1); } supervisorInfo = new SupervisorInfo(myHostName, supervisorId, portList, conf); } else { supervisorInfo = new SupervisorInfo(myHostName, supervisorId, portList, conf); } supervisorInfo.setVersion(Utils.getVersion()); String buildTs = Utils.getBuildTime(); supervisorInfo.setBuildTs(buildTs); LOG.info("jstorm version:{}, build ts:{}", supervisorInfo.getVersion(), supervisorInfo.getBuildTs()); } @SuppressWarnings("unchecked") public void update() { supervisorInfo.setTimeSecs(TimeUtils.current_time_secs()); supervisorInfo.setUptimeSecs(TimeUtils.current_time_secs() - startTime); updateSupervisorInfo(); try { stormClusterState.supervisor_heartbeat(supervisorId, supervisorInfo); } catch (Exception e) { LOG.error("Failed to update SupervisorInfo to ZK", e); } } private void updateSupervisorInfo() { Set<Integer> portList = calculateCurrentPortList(); LOG.debug("portList : {}", portList); supervisorInfo.setWorkerPorts(portList); } public void updateHealthStatus(HealthStatus status) { this.healthStatus = status; } public HealthStatus getHealthStatus() { return healthStatus; } @Override public Object getResult() { return frequency; } @Override public void run() { boolean updateHb = hbUpdateTrigger.getAndSet(false); if (updateHb) { update(); } } public int getStartTime() { return startTime; } public SupervisorInfo getSupervisorInfo() { return supervisorInfo; } public void updateHbTrigger(boolean update) { hbUpdateTrigger.set(update); } private Set<Integer> calculateCurrentPortList() { Set<Integer> defaultPortList = JStormUtils.getDefaultSupervisorPortList(conf); Set<Integer> usedList; try { usedList = getLocalAssignmentPortList(); } catch (IOException e) { supervisorInfo.setErrorMessage(null); return defaultPortList; } int availablePortNum = calculateAvailablePortNumber(defaultPortList, usedList); if (availablePortNum >= (defaultPortList.size() - usedList.size())) { supervisorInfo.setErrorMessage(null); return defaultPortList; } else { List<Integer> freePortList = new ArrayList<>(defaultPortList); freePortList.removeAll(usedList); Collections.sort(freePortList); Set<Integer> portList = new HashSet<>(usedList); for (int i = 1; i <= availablePortNum; i++) { portList.add(freePortList.get(i)); } supervisorInfo.setErrorMessage("Supervisor is lack of resources, " + "reduce the number of workers from " + defaultPortList.size() + " to " + (usedList.size() + availablePortNum)); return portList; } } private int calculateAvailablePortNumber(Set<Integer> defaultList, Set<Integer> usedList) { if (healthStatus.isMoreSeriousThan(HealthStatus.WARN)) { LOG.warn("Due to no enough resource, limit supervisor's ports and block scheduling"); // set free port number to zero return 0; } int vcores = JStormUtils.getNumProcessors(); double cpuUsage = JStormUtils.getTotalCpuUsage(); // do not adjust port list if match the following conditions if (cpuUsage <= 0.0 // non-linux, || vcores <= 4 // machine configuration is too low || !ConfigExtension.isSupervisorEnableAutoAdjustSlots(conf) // auto adjust is disabled ) { return defaultList.size() - usedList.size(); } long freeMemory = JStormUtils.getFreePhysicalMem() * 1024L; // in Byte long reserveMemory = ConfigExtension.getStormMachineReserveMem(conf); Long availablePhysicalMemorySize = freeMemory - reserveMemory; int reserveCpuUsage = ConfigExtension.getStormMachineReserveCpuPercent(conf); if (availablePhysicalMemorySize < 0 || cpuUsage + reserveCpuUsage > 100D) { // memory is not enough, or cpu is not enough, set free ports number to zero return 0; } else { int availableCpuNum = (int) Math.round((100 - cpuUsage) / 100 * vcores); return JStormUtils.getSupervisorPortNum(conf, availableCpuNum, availablePhysicalMemorySize); } } private Set<Integer> getLocalAssignmentPortList() throws IOException { Map<Integer, LocalAssignment> localAssignment; try { localAssignment = (Map<Integer, LocalAssignment>) localState.get(Common.LS_LOCAL_ASSIGNMENTS); } catch (IOException e) { LOG.error("get LS_LOCAL_ASSIGNMENTS of localState failed ."); throw e; } if (localAssignment == null) { return new HashSet<>(); } return localAssignment.keySet(); } }