public class AgentArgumentException extends Exception {

    private String err;

    /**
     * Constructs a new exception with the specified detail message.  The
     * cause is not initialized, and may subsequently be initialized by
     * a call to {@link #initCause}.
     *
     * @param err the detail message. The detail message is saved for
     *            later retrieval by the {@link #getMessage()} method.
     */
    public AgentArgumentException(String err) {
        super(err);
        this.err = err;
    }

    public String getErr() {
        return err;
    }

} public enum CounterType {
    /**
     * 用户上传什么样的值，就原封不动的存储
     */
    GAUGE,
    /**
     * 指标在存储和展现的时候，会被计算为speed，即（当前值 - 上次值）/ 时间间隔
     */
    COUNTER
} @Data
public class FalconReportObject implements Cloneable{

    /**
     *  标明Metric的主体(属主)，比如metric是cpu_idle，那么Endpoint就表示这是哪台机器的cpu_idle
     */
    private String endpoint;
    /**
     * 最核心的字段，代表这个采集项具体度量的是什么, 比如是cpu_idle呢，还是memory_free, 还是qps
     */
    private String metric;
    /**
     * 表示汇报该数据时的unix时间戳，注意是整数，代表的是秒
     */
    private long timestamp;
    /**
     * 表示该数据采集项的汇报周期，这对于后续的配置监控策略很重要，必须明确指定。
     */
    private int step;
    /**
     * 代表该metric在当前时间点的值,数值类型
     */
    private String value;
    /**
     * 只能是COUNTER或者GAUGE二选一，前者表示该数据采集项为计时器类型，后者表示其为原值 (注意大小写)
     GAUGE：即用户上传什么样的值，就原封不动的存储
     COUNTER：指标在存储和展现的时候，会被计算为speed，即（当前值 - 上次值）/ 时间间隔
     */
    private CounterType counterType;
    /**
     * 一组逗号分割的键值对, 对metric进一步描述和细化, 可以是空字符串. 比如idc=lg，比如service=xbox等，多个tag之间用逗号分割
     */
    private String tags;

    /**
     * 仅供系统使用
     */
    private ObjectName objectName;

    @Override
    public FalconReportObject clone() {
        try {
            return (FalconReportObject) super.clone();
        } catch (CloneNotSupportedException e) {
            return new FalconReportObject();
        }
    }

    /**
     * 添加tag
     * @param newTag
     * @return
     */
    public FalconReportObject appendTags(String newTag){
        if(!StringUtils.isEmpty(newTag)){
            if(StringUtils.isEmpty(this.getTags())){
                this.setTags(newTag);
            }else{
                this.setTags(this.getTags() + "," + newTag);
            }
        }
        return this;
    }

} public enum MetricsType {

    /**
     * 可用性监控值
     */
    AVAILABILITY("availability"),
    /**
     * 配置文件配置的JMX监控值
     */
    JMX_OBJECT_CONF("jmxObjectConf"),
    /**
     * 內建的JMX公共的监控属性
     */
    JMX_OBJECT_IN_BUILD("jmxObjectInBuild"),
    /**
     * 配置文件配置的通过http访问获取的监控值
     */
    HTTP_URL_CONF("httpUrlConf"),
    /**
     * 配置文件配置的SQL语句查询的监控值
     */
    SQL_CONF("sqlConf"),
    /**
     * 內建的SQL语句查询的监控值
     */
    SQL_IN_BUILD("sqlInBuild"),
    /**
     * 內建的公共的SNMP获取的监控值
     */
    SNMP_COMMON_IN_BUILD("snmpCommonInBuild"),
    /**
     * 插件內建的SNMP监控值
     */
    SNMP_Plugin_IN_BUILD("snmpPluginInBuild"),
    DETECT("detect");

    private String typeName;

    MetricsType(String typeName) {
        this.typeName = typeName;
    }

    public String getTypeName() {
        return typeName;
    }
} public interface SNMPV3Plugin extends Plugin{

    /**
     * 通过SNMPV3协议获取设备监控信息的登陆用户信息
     * @return
     * list
     * 一个 {@link com.yiji.falcon.agent.vo.snmp.SNMPV3UserInfo} 对象就是一个设备的监控
     */
    Collection<SNMPV3UserInfo> userInfo();

    /**
     * 当设备链接可用时的插件内置报告,如该插件适配的不同设备和品牌的SNMP监控报告
     * Agent会自动采集一些公共的MIB数据,但是设备私有的MIB信息,将由不同的插件自己提供
     * @param session
     * 连接设备的SNMP Session,插件可通过此对象进行设备间的SNMP通信,以获取监控数据
     * @return
     */
    Collection<FalconReportObject> inbuiltReportObjectsForValid(SNMPV3Session session);

    /**
     * 是否需要进行接口数据采集
     * @return
     */
    boolean hasIfCollect();

    /**
     * 允许采集哪些接口数据，用map的形式返回，key为接口metrics名称，value为是否允许。
     * 若对应key的metrics获取为null或false，均不采集。key的集合为：
     *
     * if.HCInBroadcastPkts
     * if.HCInMulticastPkts
     * if.HCInOctets
     * if.HCInUcastPkts
     * if.HCOutBroadcastPkts
     * if.HCOutMulticastPkts
     * if.getIfHCOutUcastPkts
     * if.OperStatus
     * if.HCOutOctets
     *
     * @return
     */
    Map<String,Boolean> ifCollectMetricsEnable();

} @Slf4j
public class SNMPHelper {

    /* 公共MIB的接口OID定义 */
    public static final String ifNameOid = "1.3.6.1.2.1.31.1.1.1.1";
    public static final String ifHCInOid = "1.3.6.1.2.1.31.1.1.1.6";
    public static final String ifHCOutOid = "1.3.6.1.2.1.31.1.1.1.10";
    public static final String ifHCInPktsOid = "1.3.6.1.2.1.31.1.1.1.7";
    public static final String ifHCOutPktsOid = "1.3.6.1.2.1.31.1.1.1.11";
    public static final String ifOperStatusOid = "1.3.6.1.2.1.2.2.1.8";
    public static final String ifHCInBroadcastPktsOid = "1.3.6.1.2.1.31.1.1.1.9";
    public static final String ifHCOutBroadcastPktsOid = "1.3.6.1.2.1.31.1.1.1.13";
    public static final String ifHCInMulticastPktsOid = "1.3.6.1.2.1.31.1.1.1.8";
    public static final String ifHCOutMulticastPktsOid = "1.3.6.1.2.1.31.1.1.1.12";
    /* 忽略的接口，如Nu匹配ifName为*Nu*的接口 */
    public static final List<String> ignoreIfName = Arrays.asList("Nu", "NU", "Vlan", "Vl", "LoopBack");

    public static final String sysDescOid = "1.3.6.1.2.1.1.1.0";

    /**
     * 判断传入的PDU是否带有可用的监控数据(PDU携带的第一个VariableBinding对象)
     * @param pdu
     * @return
     * true : 可用
     * false : 不可用
     */
    public static boolean isValidPDU(PDU pdu){
        if(pdu == null){
            return false;
        }
        VariableBinding vb = pdu.get(0);
        if(vb == null){
            return false;
        }
        String vbResult = vb.toString();
        return !"noSuchInstance".equalsIgnoreCase(vbResult) &&
                !"noSuchObject".equalsIgnoreCase(vbResult) &&
                !"noNextInstance".equalsIgnoreCase(vbResult) &&
                !"endOfView".equalsIgnoreCase(vbResult);
    }

    /**
     * 判断传入的PDU是否带有可用的监控数据
     * @param pdu
     * @param index
     * 指定的索引的VariableBinding对象
     * @return
     * true : 可用
     * false : 不可用
     */
    public static boolean isValidPDU(PDU pdu,int index){
        if(pdu == null){
            return false;
        }
        VariableBinding vb = pdu.get(index);
        if(vb == null){
            return false;
        }
        String vbResult = vb.toString();
        return !"noSuchInstance".equalsIgnoreCase(vbResult) &&
                !"noSuchObject".equalsIgnoreCase(vbResult) &&
                !"noNextInstance".equalsIgnoreCase(vbResult) &&
                !"endOfView".equalsIgnoreCase(vbResult);
    }

    /**
     * 获取传入PDU携带的MIB值(PDU携带的第一个VariableBinding对象)
     * @param pdu
     * @return
     * 若PDU无效返回空串
     */
    public static String getValueFromPDU(PDU pdu){
        if(isValidPDU(pdu)){
            return pdu.get(0).getVariable().toString();
        }
        return "";
    }

    /**
     * 获取传入PDU携带的MIB值
     * @param pdu
     * @param index
     * 指定的VariableBinding对象
     * @return
     * 若PDU无效返回空串
     */
    public static String getValueFromPDU(PDU pdu,int index){
        if(isValidPDU(pdu,index)){
            return pdu.get(index).getVariable().toString();
        }
        return "";
    }

    /**
     * 获取指定OID的 get
     *
     * @param snmp
     * @param target
     * @param oid
     * @return
     * @throws IOException
     */
    public static PDU snmpGet(Snmp snmp, Target target, String oid) throws IOException {
        ScopedPDU pdu = new ScopedPDU();
        pdu.setType(PDU.GET);
        pdu.add(new VariableBinding(new OID(oid)));

        ResponseEvent responseEvent = snmp.send(pdu, target);
        PDU response = responseEvent.getResponse();
        if(response == null){
            log.warn("response null - error:{} peerAddress:{} source:{} request:{}",
                    responseEvent.getError(),
                    responseEvent.getPeerAddress(),
                    responseEvent.getSource(),
                    responseEvent.getRequest());
        }
        return response;
    }

    /**
     * 获取指定OID的 getNext
     *
     * @param snmp
     * @param target
     * @param oid
     * @return
     * @throws IOException
     */
    public static PDU snmpGetNext(Snmp snmp, Target target, String oid) throws IOException {
        ScopedPDU pdu = new ScopedPDU();
        pdu.setType(PDU.GETNEXT);
        pdu.add(new VariableBinding(new OID(oid)));

        ResponseEvent responseEvent = snmp.send(pdu, target);
        return responseEvent.getResponse();
    }

    /**
     * walk方式获取指定的oid value
     *
     * @param snmp
     * @param target
     * @param oid
     * @return
     * @throws IOException
     */
    public static List<PDU> snmpWalk(Snmp snmp, Target target, String oid) throws IOException {
        List<PDU> pduList = new ArrayList<>();

        ScopedPDU pdu = new ScopedPDU();
        OID targetOID = new OID(oid);
        pdu.add(new VariableBinding(targetOID));

        boolean finished = false;
        while (!finished) {
            VariableBinding vb = null;
            ResponseEvent respEvent = snmp.getNext(pdu, target);

            PDU response = respEvent.getResponse();

            if (null == response) {
                break;
            } else {
                vb = response.get(0);
            }
            // check finish
            finished = checkWalkFinished(targetOID, pdu, vb);
            if (!finished) {
                pduList.add(response);

                // Set up the variable binding for the next entry.
                pdu.setRequestID(new Integer32(0));
                pdu.set(0, vb);
            }
        }

        return pduList;
    }

    private static boolean checkWalkFinished(OID targetOID, PDU pdu, VariableBinding vb) {
        boolean finished = false;
        if (pdu.getErrorStatus() != 0) {
            finished = true;
        } else if (vb.getOid() == null) {
            finished = true;
        } else if (vb.getOid().size() < targetOID.size()) {
            finished = true;
        } else if (targetOID.leftMostCompare(targetOID.size(), vb.getOid()) != 0) {
            finished = true;
        } else if (Null.isExceptionSyntax(vb.getVariable().getSyntax())) {
            finished = true;
        } else if (vb.getOid().compareTo(targetOID) <= 0) {
            finished = true;
        }
        return finished;

    }

    /**
     * 根据系统信息获取供应商
     *
     * @param sysDesc
     * @return
     */
    public static VendorType getVendorBySysDesc(String sysDesc) {
        if(StringUtils.isEmpty(sysDesc)){
            return null;
        }
        String sysDescLower = sysDesc.toLowerCase();

        if (sysDescLower.contains("cisco nx-os")) {
            return VendorType.Cisco_NX;
        }

        if (sysDesc.contains("Cisco Internetwork Operating System Software") && sysDesc.contains("C12K")) {
            return VendorType.Cisco_12K;
        }

        if (sysDescLower.contains("cisco ios")) {
            if (sysDesc.contains("IOS-XE Software")) {
                return VendorType.Cisco_IOS_XE;
            } else if (sysDesc.contains("Cisco IOS XR")) {
                return VendorType.Cisco_IOS_XR;
            } else {
                return VendorType.Cisco;
            }
        }

        if (sysDescLower.contains("cisco adaptive security appliance")) {
            float version = getVersionBySysDesc(sysDesc);
            if (version != 0 && version < 9.2) {
                return VendorType.Cisco_ASA_OLD;
            }
            return VendorType.Cisco_ASA;
        }

        if (sysDescLower.contains("cisco internetwork operating system software") &&
                sysDescLower.contains("7200 software")) {
            return VendorType.Cisco_IOS_7200;
        }

        if (sysDescLower.contains("h3c")) {
            if (sysDesc.contains("Software Version 5")) {
                return VendorType.H3C_V5;
            } else if (sysDesc.contains("Software Version 7")) {
                return VendorType.H3C_V7;
            } else if (sysDesc.contains("Version S9500")) {
                return VendorType.H3C_S9500;
            }
            return VendorType.H3C;
        }

        if (sysDescLower.contains("huawei")) {
            if (sysDesc.contains("MultiserviceEngine 60")) {
                return VendorType.Huawei_ME60;
            } else if (sysDesc.contains("Version 5.70")) {
                return VendorType.Huawei_V5_70;
            } else if (sysDesc.contains("Version 5.130")) {
                return VendorType.Huawei_V5_130;
            } else if (sysDesc.contains("Version 3.10")) {
                return VendorType.Huawei_V3_10;
            }
            return VendorType.Huawei;
        }

        if (sysDescLower.contains("ruijie")) {
            return VendorType.Ruijie;
        }

        if (sysDescLower.contains("juniper networks")) {
            return VendorType.Juniper;
        }

        if (sysDescLower.contains("linux")) {
            return VendorType.Linux;
        }

        return VendorType.UNKNOWN;
    }

    /**
     * 根据系统描述信息获取版本号
     *
     * @param sysDesc
     * @return
     */
    public static float getVersionBySysDesc(String sysDesc) {
        if(StringUtils.isEmpty(sysDesc)){
            return 0;
        }
        float version = 0;
        String versionStr = "";
        String[] ss = sysDesc.split("\\s+");
        for (int i = 0; i < ss.length; i++) {
            if ("version".equals(ss[i].toLowerCase())) {
                versionStr = ss[i + 1];
            }
        }
        versionStr = versionStr.replace("(", "").replace(")", "");

        try {
            version = Float.parseFloat(versionStr);
        } catch (NumberFormatException ignored) {
        }

        return version;
    }

} @Slf4j
public class SNMPV3Session {

//    private static final String cacheKey_equipmentName = "equipmentName";
    private static final String cacheKey_sysDesc = "sysDesc";
    private static final String cacheKey_sysVendor = "sysVendor";
    private static final String cacheKey_version = "version";

    private Snmp snmp;
    private UserTarget target;
    private SNMPV3UserInfo userInfo;
    //信息缓存
    private final ConcurrentHashMap<String,Object> infoCache = new ConcurrentHashMap<>();

    @Override
    public String toString() {
        return "SNMPV3Session{" +
                "snmp=" + snmp +
                ", target=" + target +
                ", userInfo=" + userInfo +
                '}';
    }

    /**
     * 创建SNMPV3会话
     * @param userInfo
     * @throws IOException
     * @throws AgentArgumentException
     */
    public SNMPV3Session(SNMPV3UserInfo userInfo) throws IOException, AgentArgumentException {
        if(StringUtils.isEmpty(userInfo.getAddress())){
            throw new AgentArgumentException("SNMPV3Session创建失败:snmp v3协议的访问地址不能为空");
        }
        if(StringUtils.isEmpty(userInfo.getUsername())){
            throw new AgentArgumentException("SNMPV3Session创建失败:snmp v3协议的访问用户名不能为空");
        }
        if(!StringUtils.isEmpty(userInfo.getAythType()) && StringUtils.isEmpty(userInfo.getAuthPswd())){
            throw new AgentArgumentException("SNMPV3Session创建失败:snmp v3协议指定了认证算法 aythType,就必须要指定认证密码");
        }
        if(!StringUtils.isEmpty(userInfo.getPrivType()) && StringUtils.isEmpty(userInfo.getPrivPswd())){
            throw new AgentArgumentException("SNMPV3Session创建失败:snmp v3协议指定了加密算法 privType,就必须要指定加密密码");
        }

        this.userInfo = userInfo;
        snmp = new Snmp(new DefaultUdpTransportMapping());
        USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
        SecurityModels.getInstance().addSecurityModel(usm);
        snmp.listen();

        UsmUser user = new UsmUser(
                new OctetString(userInfo.getUsername()),
                getAuthProtocol(userInfo.getAythType()), new OctetString(userInfo.getAuthPswd()),
                getPrivProtocol(userInfo.getPrivType()), new OctetString(userInfo.getPrivPswd()));

        snmp.getUSM().addUser(new OctetString(userInfo.getUsername()), user);

        target = new UserTarget();
        target.setSecurityName(new OctetString(userInfo.getUsername()));
        target.setVersion(SnmpConstants.version3);
        target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
        target.setAddress(GenericAddress.parse(userInfo.getProtocol() + ":" + userInfo.getAddress() + "/" + userInfo.getPort()));
        target.setTimeout(8000);
        target.setRetries(1);
    }

    /**
     * 获取该会话是否可用
     * @return
     */
    public boolean isValid(){
        try {
            //超时将会返回null
            return SNMPHelper.snmpGet(snmp,target,SNMPHelper.sysDescOid) != null;
        } catch (Exception e) {
            log.error("SNMP连接({}:{}-{})可用性检测为失败(不可用)",userInfo.getAddress(),userInfo.getPort(),userInfo.getEndPoint(),e);
            return false;
        }
    }

    private String getInfoCacheKey(){
        return this.getUserInfo().getAddress() + "-" + this.getUserInfo().getPort();
    }

    /**
     * 获取设备名称
     * ip-vendor-version
     *
     * @return
     */
    public String getAgentSignName() {
        //去掉agentSignName，使用配置（authorization.properties）中的自定义endPoint来识别设备
        return "";
//        String key = getInfoCacheKey() + cacheKey_equipmentName;
//
//        if(infoCache.get(key) != null){
//            //已有有效的设备名称缓存,直接返回
//            return (String) infoCache.get(getInfoCacheKey());
//        }
//        String prefix = this.getUserInfo().getAddress() + "-" + this.getUserInfo().getPort();
//        try {
//            if(isValid()){
//                String name = prefix + "-" + getSysVendor() + "-" + getSysVersion();
//                infoCache.put(key,name);
//                return name;
//            }else{
//                return prefix;
//            }
//        } catch (IOException e) {
//            log.error("设备描述信息获取失败",e);
//            return prefix;
//        }
    }

    /**
     * 获取设备描述字符串
     *
     * @return
     * @throws IOException
     */
    public String getSysDesc() throws IOException {
        String key = getInfoCacheKey() + cacheKey_sysDesc;
        String sysDesc = (String) infoCache.get(key);
        if(sysDesc != null){
            return sysDesc;
        }
        PDU pdu = this.get(SNMPHelper.sysDescOid);
        if(pdu != null){
            sysDesc = pdu.get(0).getVariable().toString();
            infoCache.put(key,sysDesc);
            return sysDesc;
        }else {
            return "";
        }

    }

    /**
     * 获取设备的供应商
     * @return
     * null : 获取失败
     * @throws IOException
     */
    public VendorType getSysVendor() throws IOException {
        String key = getInfoCacheKey() + cacheKey_sysVendor;
        VendorType sysVendor = (VendorType) infoCache.get(key);
        if(sysVendor != null){
            return sysVendor;
        }
        sysVendor = SNMPHelper.getVendorBySysDesc(getSysDesc());
        if(sysVendor == null){
            return null;
        }
        infoCache.put(key,sysVendor);
        return sysVendor;
    }

    /**
     * 获取设备版本
     * @return
     * 0 : 获取失败
     * @throws IOException
     */
    public float getSysVersion() throws IOException {
        String key = getInfoCacheKey() + cacheKey_version;
        Float version = (Float) infoCache.get(key);
        if(version != null){
            return version;
        }
        version = SNMPHelper.getVersionBySysDesc(getSysDesc());
        if(version == 0){
            return 0;
        }
        infoCache.put(key,version);
        return version;
    }

    /**
     * get方法获取指定OID
     * @param oid
     * @return
     * @throws IOException
     */
    public PDU get(String oid) throws IOException {
        return SNMPHelper.snmpGet(snmp,target,oid);
    }

    /**
     * getNext方法获取指定OID
     * @param oid
     * @return
     * @throws IOException
     */
    public PDU getNext(String oid) throws IOException {
        return SNMPHelper.snmpGetNext(snmp,target,oid);
    }

    /**
     * 对指定的OID进行子树walk,并返回所有的walk结果
     * @param oid
     * @return
     * @throws IOException
     */
    public List<PDU> walk(String oid) throws IOException {
        return SNMPHelper.snmpWalk(snmp,target,oid);
    }

    /**
     * 获取指定的认证算法
     * @param privType
     * @return
     * @throws AgentArgumentException
     */
    private OID getPrivProtocol(String privType) throws AgentArgumentException {

        if (privType == null
                || privType.equalsIgnoreCase("none")
                || privType.length() == 0) {
            return null;
        }

        switch (privType) {
            case "DES":
                return PrivDES.ID;
            case "3DES":
                return Priv3DES.ID;
            case "AES128":
            case "AES-128":
            case "AES":
                return PrivAES128.ID;
            case "AES192":
            case "AES-192":
                return PrivAES192.ID;
            case "AES256":
            case "AES-256":
                return PrivAES256.ID;
            default:
                throw new AgentArgumentException("Privacy protocol " + privType + " not supported");
        }
    }

    /**
     * 获取指定的加密算法
     * @param authMethod
     * @return
     * @throws AgentArgumentException
     */
    private OID getAuthProtocol(String authMethod) throws AgentArgumentException {
        if (authMethod == null
                || authMethod.equalsIgnoreCase("none")
                || authMethod.length() == 0) {
            return null;
        } else if (authMethod.equalsIgnoreCase("md5")) {
            return AuthMD5.ID;
        } else if (authMethod.equalsIgnoreCase("sha")) {
            return AuthSHA.ID;
        } else {
            throw new AgentArgumentException("unknown authentication protocol: " + authMethod);
        }
    }

    public void close() throws IOException {
        log.debug("关闭snmp连接{}",userInfo.getAddress());
        snmp.close();
    }

    public SNMPV3UserInfo getUserInfo() {
        return userInfo;
    }
} @Slf4j
public class CommandUtilForUnix {

    /**
     * 命令执行的返回结果值类
     */
    public static class ExecuteResult{
        /**
         * 命令是否执行成功
         */
        public boolean isSuccess = false;
        /**
         * 是否正常执行结束
         * 如结果读取超时、命令执行失败等都是false
         */
        public boolean isNormalExit = false;
        /**
         * 执行命令的返回结果
         */
        public String msg = "";

        public ExecuteResult(boolean isSuccess, boolean isNormalExit, String msg) {
            this.isSuccess = isSuccess;
            this.isNormalExit = isNormalExit;
            this.msg = msg;
        }

        @Override
        public String toString() {
            return "ExecuteResult{" +
                    "isSuccess=" + isSuccess +
                    ", isNormalExit=" + isNormalExit +
                    ", msg='" + msg + '\'' +
                    '}';
        }
    }

    /**
     * 获取指定进程的命令行目录
     * @param pid
     * @return
     * @throws IOException
     */
    public static String getCmdDirByPid(int pid) throws IOException {
        String cmd = "ls -al /proc/" + pid;
        CommandUtilForUnix.ExecuteResult executeResult = CommandUtilForUnix.execWithReadTimeLimit(cmd,false,7);
        String msg = executeResult.msg;
        String[] ss = msg.split("\n");
        for (String s : ss) {
            if(s.toLowerCase().contains("cwd")){
                String[] split = s.split("\\s+");
                return split[split.length - 1];
            }
        }

        return null;
    }


    /**
     * 执行命令,给定最大的命令结果读取时间(秒)，实际等待读取时间可能会大于指定时间，最大可能为2倍
     * @param execTarget
     * @param cmd
     * @param cmdSep
     * 是否对cmd进行空格分隔
     * @param maxReadTime
     * @return
     * @throws IOException
     */
    public static ExecuteResult execWithReadTimeLimit(String execTarget,String cmd,boolean cmdSep, long maxReadTime){
        final List<Process> resultList = new ArrayList<>();
        final BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(1);
        StringBuilder resultMsg = new StringBuilder();
        ExecuteThreadUtil.execute(() -> {
            Process process = null;
            try {
                process = exec(resultMsg,execTarget,cmd,cmdSep,maxReadTime, TimeUnit.SECONDS);
            } catch (IOException e) {
                log.error("Command '{}' execute exception",cmd,e);
            }
            resultList.add(process);
            if (!blockingQueue.offer(resultList.get(0)))
                log.error("阻塞队列插入失败");
        });

        try {
            Object result = blockingQueue.take();
            if (result instanceof Process){
                //命令正常返回
                Process process = (Process) result;
                if(process.isAlive()){
                    log.debug("process destroyForcibly");
                   process.destroyForcibly();
                }
                boolean exeSuccess = process.exitValue() == 0;
                if(!exeSuccess){
                    log.error("命令 {} 执行错误：{}",cmd,resultMsg.toString());
                }
                return new ExecuteResult(resultMsg.length() > 0 ,exeSuccess,String.valueOf(resultMsg.toString()));
            }else{
                log.error("Unknown Result Type Mapper",result);
                return new ExecuteResult(resultMsg.length() > 0,false,resultMsg.toString());
            }
        } catch (InterruptedException e) {
            log.warn("Command '{}' execute exception",cmd,e);
            return new ExecuteResult(resultMsg.length() > 0,false,resultMsg.toString());
        }

    }

    /**
     * 执行命令,给定最大的命令结果读取时间
     * @param cmd
     * @param cmdSep
     * 是否对cmd进行空格分隔
     * @param maxReadTime
     * @return
     * @throws IOException
     */
    public static ExecuteResult execWithReadTimeLimit(String cmd,boolean cmdSep, long maxReadTime) throws IOException {
        return execWithReadTimeLimit(null,cmd,cmdSep,maxReadTime);
    }

    /**
     * 执行命令
     * @param result
     * 记录返回值
     * @param execTarget
     * 执行体
     * 默认 /bin/sh
     * @param cmd
     * 待执行的命令
     * @param cmdSep
     * 是否对cmd进行空格分隔
     * @return
     * @throws IOException
     */
    private static Process exec(StringBuilder result,String execTarget,String cmd,boolean cmdSep,long timeout,TimeUnit timeUnit) throws IOException {
        List<String> shList = new ArrayList<>();
        if(execTarget == null){
            execTarget = "/bin/sh";
            shList.add(execTarget);
            shList.add("-c");
            log.info("执行命令 : {} -c \"{}\"",execTarget,cmd);
        }else{
            shList.add(execTarget);
            log.info("执行命令 : {} {}",execTarget,cmd);
        }
        if(cmdSep){
            Collections.addAll(shList, cmd.split("\\s"));
        }else{
            shList.add(cmd);
        }

        ProcessBuilder pb = new ProcessBuilder(shList);
        Process process = pb.start();

        try(ByteArrayOutputStream resultOutStream = new ByteArrayOutputStream();
            InputStream errorInStream = new BufferedInputStream(process.getErrorStream());
            InputStream processInStream = new BufferedInputStream(process.getInputStream())){

            Callable readTask1 = new Callable<String>(){
                @Override
                public String call() throws Exception {
                    readFromStream(result,errorInStream,resultOutStream);
                    return "";
                }
            };

            Callable readTask2 = new Callable<String>() {
                @Override
                public String call() throws Exception {
                    readFromStream(result,processInStream,resultOutStream);
                    return "";
                }
            };

            Future readFuture1 = ExecuteThreadUtil.execute(readTask1);
            Future readFuture2 = ExecuteThreadUtil.execute(readTask2);

            try {
                readFuture1.get(timeout,timeUnit);
                readFuture2.get(timeout,timeUnit);
            } catch (Exception e) {
                log.info("Command '{}' read timeout {} {}",cmd,timeout,timeUnit.name());
            }finally {
                process.destroy();
                try {
                    process.waitFor();
                } catch (InterruptedException e) {
                    log.error("",e);
                }
            }
        }

        return process;
    }

    private static void readFromStream(StringBuilder result,InputStream inputStream,ByteArrayOutputStream resultOutStream) throws IOException {
        int num;
        byte[] bs = new byte[1024];
        while ((num = inputStream.read(bs)) != -1) {
            resultOutStream.write(bs, 0, num);
            result.append(new String(resultOutStream.toByteArray(),"utf-8"));
            resultOutStream.reset();
        }
    }

    /**
     * 进行Ping探测
     * @param address
     * 需要Ping的地址
     * @param count
     * Ping的次数
     * @return
     * 返回Ping的平均延时
     * 返回 -1 代表 Ping超时
     * 返回 -2 代表 命令执行失败
     */
    public static PingResult ping(String address,int count) throws IOException {
        PingResult pingResult = new PingResult();
        String commend = String.format("ping -c %d %s",count,address);
        CommandUtilForUnix.ExecuteResult executeResult = CommandUtilForUnix.execWithReadTimeLimit(commend,false,30);

        if(executeResult.isSuccess){
            List<Float> times = new ArrayList<>();
            String msg = executeResult.msg;
            for (String line : msg.split("\n")) {
                for (String ele : line.split(" ")) {
                    if(ele.toLowerCase().contains("time=")){
                        float time = Float.parseFloat(ele.replace("time=",""));
                        times.add(time);
                    }
                }
            }

            if(times.isEmpty()){
                log.warn(String.format("ping 地址 %s 无法连通",address));
                pingResult.resultCode = -1;
            }else{
                float sum = 0;
                for (Float time : times) {
                    sum += time;
                }
                pingResult.resultCode = 1;
                pingResult.avgTime = Maths.div(sum,times.size(),3);
                pingResult.successCount = times.size();
            }

        }else{
            log.error("命令{}执行失败",commend);
            pingResult.resultCode = -2;
        }

        return pingResult;
    }

    /**
     * 从 /etc/profile 文件获取JAVA_HOME
     * @return
     * null : 获取失败
     * @throws IOException
     */
    public static String getJavaHomeFromEtcProfile() throws IOException {
        ExecuteResult executeResult = execWithReadTimeLimit("cat /etc/profile",false,7);
        if(!executeResult.isSuccess){
            return null;
        }
        String msg = executeResult.msg;
        StringTokenizer st = new StringTokenizer(msg,"\n",false);
        while( st.hasMoreElements() ){
            String split = st.nextToken().trim();
            if(!StringUtils.isEmpty(split)){
                if(split.contains("JAVA_HOME=")){
                    String[] ss = split.split("=");
                    List<String> list = new ArrayList<>();
                    for (String s : ss) {
                        if(!StringUtils.isEmpty(s)){
                            list.add(s);
                        }
                    }
                    return list.get(list.size() - 1);
                }
            }
        }

        return null;
    }

    public static class PingResult{
        /**
         * 执行结果
         * 返回  1 代表执行成功
         * 返回 -1 代表 Ping超时
         * 返回 -2 代表 命令执行失败
         */
        public int resultCode;
        /**
         * 成功返回ping延迟的次数
         */
        public int successCount;
        /**
         * ping的平均延迟值
         */
        public double avgTime;

        @Override
        public String toString() {
            return "PingResult{" +
                    "resultCode=" + resultCode +
                    ", successCount=" + successCount +
                    ", avgTime=" + avgTime +
                    '}';
        }
    }

} public class StringUtils {

    /**
     * 判断传入的String是否为空('' or null)
     * @param str
     * @return
     */
    public static boolean isEmpty(String str){
        return str == null || str.length() == 0;
    }

    /**
     * 返回指定数字的字符串形式
     * @param value
     * @return
     */
    public static String getStringByInt(int value){
        return String.valueOf(value).intern();
    }

} @Data
public class IfStatVO {
    private String ifName;
    private int ifIndex;
    private String ifHCInOctets;
    private String ifHCOutOctets;
    private String ifHCInUcastPkts;
    private String ifHCOutUcastPkts;
    private String ifHCInBroadcastPkts;
    private String ifHCOutBroadcastPkts;
    private String ifHCInMulticastPkts;
    private String ifHCOutMulticastPkts;
    private String ifOperStatus;
    private Date time;

} @Data
public class SNMPV3UserInfo {

    /**
     * 网络协议 如udp、tcp
     */
    private String protocol = "udp";
    /**
     * 网络设备的IP地址
     */
    private String address = "";
    /**
     * 网络设备的连接端口号
     */
    private String port = "161";
    /**
     * 用户名
     */
    private String username = "";
    /**
     * 认证算法,如 none,MD5,SHA
     */
    private String aythType = "";
    /**
     * 认证密码
     */
    private String authPswd = "";
    /**
     * 加密算法,如 none,DES,3DES,AES128,AES,AES192,AES256
     */
    private String privType = "";
    /**
     * 加密密码
     */
    private String privPswd = "";
    /**
     * endPoint
     */
    private String endPoint = "";

    /**
     * 允许采集哪些名称的接口数据
     * 注：只采集集合中的接口名称的数据。若为空，则不会采集接口数据
     */
    private List<String> ifCollectNameEnables;

} public static final List<String> ignoreIfName = Arrays.asList("Nu", "NU", "Vlan", "Vl", "LoopBack");
import com.yiji.falcon.agent.exception.AgentArgumentException;
import com.yiji.falcon.agent.falcon.CounterType;
import com.yiji.falcon.agent.falcon.FalconReportObject;
import com.yiji.falcon.agent.falcon.MetricsType;
import com.yiji.falcon.agent.plugins.SNMPV3Plugin;
import com.yiji.falcon.agent.plugins.util.SNMPHelper;
import com.yiji.falcon.agent.plugins.util.SNMPV3Session;
import com.yiji.falcon.agent.util.CommandUtilForUnix;
import com.yiji.falcon.agent.util.StringUtils;
import com.yiji.falcon.agent.vo.snmp.IfStatVO;
import com.yiji.falcon.agent.vo.snmp.SNMPV3UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU;
import org.snmp4j.smi.VariableBinding;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import static com.yiji.falcon.agent.plugins.util.SNMPHelper.ignoreIfName;
                        statVO.setIfHCInOctets(SNMPHelper.getValueFromPDU(ifIn));
                    }
                    if (hasIfCollection("if.HCOutOctets")) {
                        PDU ifOut = session.get(SNMPHelper.ifHCOutOid + "." + index);
                        statVO.setIfHCOutOctets(SNMPHelper.getValueFromPDU(ifOut));
                    }
                    if (hasIfCollection("if.HCInUcastPkts")) {
                        PDU ifHCIn = session.get(SNMPHelper.ifHCInPktsOid + "." + index);
                        statVO.setIfHCInUcastPkts(SNMPHelper.getValueFromPDU(ifHCIn));
                    }
                    if (hasIfCollection("if.getIfHCOutUcastPkts")) {
                        PDU ifHCOut = session.get(SNMPHelper.ifHCOutPktsOid + "." + index);
                        statVO.setIfHCOutUcastPkts(SNMPHelper.getValueFromPDU(ifHCOut));
                    }
                    if (hasIfCollection("if.OperStatus")) {
                        PDU ifOperStatus = session.get(SNMPHelper.ifOperStatusOid + "." + index);
                        statVO.setIfOperStatus(SNMPHelper.getValueFromPDU(ifOperStatus));
                    }
                    if (hasIfCollection("if.HCOutBroadcastPkts")) {
                        PDU ifHCOutBroadcast = session.get(SNMPHelper.ifHCOutBroadcastPktsOid + "." + index);
                        statVO.setIfHCOutBroadcastPkts(SNMPHelper.getValueFromPDU(ifHCOutBroadcast));
                    }
                    if (hasIfCollection("if.HCOutMulticastPkts")) {
                        PDU ifHCOutMulticast = session.get(SNMPHelper.ifHCOutMulticastPktsOid + "." + index);
                        statVO.setIfHCOutMulticastPkts(SNMPHelper.getValueFromPDU(ifHCOutMulticast));
                    }
                    statVO.setTime(new Date());

                    statVOs.add(statVO);
                }
            }

            for (IfStatVO statVO : statVOs) {
                FalconReportObject reportObject = new FalconReportObject();
                MetricsCommon.setReportCommonValue(reportObject, plugin.step());
                reportObject.appendTags(MetricsCommon.getTags(session.getAgentSignName(), plugin, plugin.serverName(), MetricsType.SNMP_COMMON_IN_BUILD));
                reportObject.setCounterType(CounterType.COUNTER);
                String endPoint = userInfo.getEndPoint();
                if (!StringUtils.isEmpty(endPoint)) {
                    //设置单独设置的endPoint
                    reportObject.setEndpoint(endPoint);
                    reportObject.appendTags("customerEndPoint=true");
                }

                String ifName = statVO.getIfName();
                long time = statVO.getTime().getTime() / 1000;
                reportObject.appendTags("ifName=" + ifName);

                reportObject.setMetric("if.HCInBroadcastPkts");
                reportObject.setValue(statVO.getIfHCInBroadcastPkts());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                reportObject.setMetric("if.HCInMulticastPkts");
                reportObject.setValue(statVO.getIfHCInMulticastPkts());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                reportObject.setMetric("if.HCInOctets");
                reportObject.setValue(statVO.getIfHCInOctets());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                reportObject.setMetric("if.HCInUcastPkts");
                reportObject.setValue(statVO.getIfHCInUcastPkts());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                reportObject.setMetric("if.HCOutBroadcastPkts");
                reportObject.setValue(statVO.getIfHCOutBroadcastPkts());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                reportObject.setMetric("if.HCOutMulticastPkts");
                reportObject.setValue(statVO.getIfHCOutMulticastPkts());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                reportObject.setMetric("if.getIfHCOutUcastPkts");
                reportObject.setValue(statVO.getIfHCOutUcastPkts());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                reportObject.setMetric("if.HCOutOctets");
                reportObject.setValue(statVO.getIfHCOutOctets());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());

                //放在最后，设置GAUGE类型
                reportObject.setCounterType(CounterType.GAUGE);

                reportObject.setMetric("if.OperStatus");
                reportObject.setValue(statVO.getIfOperStatus());
                reportObject.setTimestamp(time);
                reportObjects.add(reportObject.clone());
            }
        }

        return reportObjects.stream().filter(falconReportObject -> falconReportObject.getValue() != null).collect(Collectors.toList());

    }

    /**
     * ping操作
     *
     * @param session
     * @param count
     * @return
     */
    public FalconReportObject ping(SNMPV3Session session, int count) {
        FalconReportObject reportObject = new FalconReportObject();
        MetricsCommon.setReportCommonValue(reportObject, plugin.step());
        reportObject.appendTags(MetricsCommon.getTags(session.getAgentSignName(), plugin, plugin.serverName(), MetricsType.SNMP_COMMON_IN_BUILD));
        reportObject.setCounterType(CounterType.GAUGE);
        reportObject.setMetric("pingAvgTime");
        reportObject.setTimestamp(System.currentTimeMillis() / 1000);

        String address = session.getUserInfo().getAddress();

        try {
            CommandUtilForUnix.PingResult pingResult = CommandUtilForUnix.ping(address, count);