content: package com.sohu.cache.client.service.impl;
  
  import java.text.DecimalFormat;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.collections.MapUtils;
  import org.apache.commons.lang.StringUtils;
  import org.apache.commons.lang.math.NumberUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.sohu.cache.client.service.AppInstanceClientRelationService;
  import com.sohu.cache.client.service.ClientReportCostDistriService;
  import com.sohu.cache.client.service.ClientReportInstanceService;
  import com.sohu.cache.dao.AppClientCostTimeStatDao;
  import com.sohu.cache.dao.AppClientCostTimeTotalStatDao;
  import com.sohu.cache.entity.AppClientCostTimeStat;
  import com.sohu.cache.entity.AppClientCostTimeTotalStat;
  import com.sohu.cache.entity.InstanceInfo;
  import com.sohu.tv.jedis.stat.constant.ClientReportConstant;
  import com.sohu.tv.jedis.stat.enums.ClientCollectDataTypeEnum;
  import com.sohu.tv.jedis.stat.model.ClientReportBean;
  
  /**
   * 客户端上报耗时分布
   * 
   * @author leifu
   * @Date 2015年1月19日
   * @Time 下午1:49:39
   */
  public class ClientReportCostDistriServiceImpl implements ClientReportCostDistriService {
  
      private final Logger logger = LoggerFactory.getLogger(ClientReportCostDistriServiceImpl.class);
  
      /**
       * 客户端耗时操作
       */
      private AppClientCostTimeStatDao appClientCostTimeStatDao;
      
      /**
       * 基于应用的客户端耗时操作
       */
      private AppClientCostTimeTotalStatDao appClientCostTimeTotalStatDao;
  
      /**
       * host:port与instanceInfo简单缓存
       */
      private ClientReportInstanceService clientReportInstanceService;
      
      /**
       * 应用下节点和客户端关系
       */
      private AppInstanceClientRelationService appInstanceClientRelationService;
  
      @Override
      public List<String> getAppDistinctCommand(Long appId, long startTime, long endTime) {
          try {
              return appClientCostTimeTotalStatDao.getAppDistinctCommand(appId, startTime, endTime);
          } catch (Exception e) {
              logger.error(e.getMessage(), e);
              return Collections.emptyList();
          }
      }
      
      @Override
      public List<AppClientCostTimeStat> getAppCommandClientToInstanceStat(Long appId, String command, Long instanceId,
              String clientIp, long startTime, long endTime) {
          try {
              return appClientCostTimeStatDao.getAppCommandClientToInstanceStat(appId, command, instanceId, clientIp,
                      startTime, endTime);
          } catch (Exception e) {
              logger.error(e.getMessage(), e);
              return Collections.emptyList();
          }
      }
      
      @Override
      public List<AppClientCostTimeTotalStat> getAppClientCommandTotalStat(Long appId, String command, long startTime,
              long endTime) {
          try {
              return appClientCostTimeTotalStatDao.getAppClientCommandStat(appId, command, startTime, endTime);
          } catch (Exception e) {
              logger.error(e.getMessage(), e);
              return Collections.emptyList();
          }
      }
      
      @Override
      public void batchSave(ClientReportBean clientReportBean) {
          try {
              // 1.client上报
              final String clientIp = clientReportBean.getClientIp();
              final long collectTime = clientReportBean.getCollectTime();
              final long reportTime = clientReportBean.getReportTimeStamp();
              final List<Map<String, Object>> datas = clientReportBean.getDatas();
              if (datas == null || datas.isEmpty()) {
                  logger.warn("datas field {} is empty", clientReportBean);
                  return;
              }
              
              // 2.结果集
              List<AppClientCostTimeStat> appClientCostTimeStatList = new ArrayList<AppClientCostTimeStat>();
  
              // 3.解析结果
              for (Map<String, Object> map : datas) {
                  Integer clientDataType = MapUtils.getInteger(map, ClientReportConstant.CLIENT_DATA_TYPE, -1);
                  ClientCollectDataTypeEnum clientCollectDataTypeEnum = ClientCollectDataTypeEnum.MAP.get(clientDataType);
                  if (clientCollectDataTypeEnum == null) {
                      continue;
                  }
                  if (ClientCollectDataTypeEnum.COST_TIME_DISTRI_TYPE.equals(clientCollectDataTypeEnum)) {
                      AppClientCostTimeStat appClientCostTimeStat = generate(clientIp, collectTime, reportTime, map);
                      if (appClientCostTimeStat != null) {
                          appClientCostTimeStatList.add(appClientCostTimeStat);
                      }
                  }
              }
              
              if (CollectionUtils.isNotEmpty(appClientCostTimeStatList)) {
                  // 4.批量保存
                  appClientCostTimeStatDao.batchSave(appClientCostTimeStatList);
                  // 5.合并app统计结果
                  List<AppClientCostTimeTotalStat> appClientCostTimeTotalStatList = mergeAppClientCostTimeStat(appClientCostTimeStatList);
                  if (CollectionUtils.isNotEmpty(appClientCostTimeTotalStatList)) {
                      appClientCostTimeTotalStatDao.batchSave(appClientCostTimeTotalStatList);
                  }
                  // 6.保存应用下节点和客户端关系
                  appInstanceClientRelationService.batchSave(appClientCostTimeStatList);
              }
          } catch (Exception e) {
              logger.error(e.getMessage(), e);
          }
      }
      
      /**
       * 合并以app为单位的
       * @param appClientCostTimeStatList
       */
      private List<AppClientCostTimeTotalStat> mergeAppClientCostTimeStat(List<AppClientCostTimeStat> appClientCostTimeStatList) {
          // 1. merge后的结果
          List<AppClientCostTimeTotalStat> resultList = new ArrayList<AppClientCostTimeTotalStat>();
          
          // 2. 以appid_command_collectTime为key，拆分appClientCostTimeStatList
          Map<String, List<AppClientCostTimeStat>> map = new HashMap<String, List<AppClientCostTimeStat>>();
          for (AppClientCostTimeStat appClientCostTimeStat : appClientCostTimeStatList) {
              long appId = appClientCostTimeStat.getAppId();
              String command = appClientCostTimeStat.getCommand();
              long collectTime = appClientCostTimeStat.getCollectTime();
              String key = appId + "_" + command + "_" + collectTime;
              if (map.containsKey(key)) {
                  map.get(key).add(appClientCostTimeStat);
              } else {
                  List<AppClientCostTimeStat> list = new ArrayList<AppClientCostTimeStat>();
                  list.add(appClientCostTimeStat);
                  map.put(key, list);
              }
          }
          
          // 3.生成结果
          for (Entry<String, List<AppClientCostTimeStat>> entry : map.entrySet()) {
              String key = entry.getKey();
              String[] items = key.split("_");
              long appId = NumberUtils.toLong(items[0]);
              String command = items[1];
              long collectTime = NumberUtils.toLong(items[2]);
              
              double totalCost = 0.0;
              long totalCount = 0;
              int median = 0;
              int ninetyPercentMax = 0;
              int ninetyNinePercentMax = 0;
              int hundredMax = 0;
              String maxInstanceHost = "";
              int maxInstancePort = 0;
              long maxInstanceId = 0;
              String maxClientIp = "";
              double mean = 0.0;
              for (AppClientCostTimeStat appClientCostTimeStat : entry.getValue()) {
                  AppClientCostTimeTotalStat appClientCostTimeTotalStat = AppClientCostTimeTotalStat.getFromAppClientCostTimeStat(appClientCostTimeStat);
                  totalCost += appClientCostTimeTotalStat.getTotalCost();
                  totalCount += appClientCostTimeTotalStat.getTotalCount();
                  if (appClientCostTimeTotalStat.getMedian() > median) {
                      median = appClientCostTimeTotalStat.getMedian();
                  }
                  if (appClientCostTimeTotalStat.getNinetyPercentMax() > ninetyPercentMax) {
                      ninetyPercentMax = appClientCostTimeTotalStat.getNinetyPercentMax();
                  }
                  if (appClientCostTimeTotalStat.getNinetyNinePercentMax() > ninetyNinePercentMax) {
                      ninetyNinePercentMax = appClientCostTimeTotalStat.getNinetyNinePercentMax();
                  }
                  if (appClientCostTimeTotalStat.getHundredMax() > hundredMax) {
                      hundredMax = appClientCostTimeTotalStat.getHundredMax();
                      maxInstanceHost = appClientCostTimeTotalStat.getMaxInstanceHost();
                      maxInstancePort = appClientCostTimeTotalStat.getMaxInstancePort();
                      maxInstanceId = appClientCostTimeTotalStat.getMaxInstanceId();
                      maxClientIp = appClientCostTimeTotalStat.getMaxClientIp();
                  }
              }
              DecimalFormat df = new DecimalFormat("0.00");
              totalCost = NumberUtils.toDouble(df.format(totalCost));
              
              //平均值
              if (totalCount > 0) {
                  mean = totalCost / totalCount;
                  mean = NumberUtils.toDouble(df.format(mean));
              }
              
              //添加到结果集
              resultList.add(new AppClientCostTimeTotalStat(-1, appId, collectTime, new Date(), command,
                      totalCount, totalCost, median, mean, ninetyPercentMax, ninetyNinePercentMax, hundredMax,
                      maxInstanceHost, maxInstancePort, maxInstanceId, maxClientIp));
          }
          return resultList;
      }
      
      private AppClientCostTimeStat generate(String clientIp, long collectTime, long reportTime, Map<String, Object> map) {
          try {
              Integer count = MapUtils.getInteger(map, ClientReportConstant.COST_COUNT, 0);
              String command = MapUtils.getString(map, ClientReportConstant.COST_COMMAND, "");
              if (StringUtils.isBlank(command)) {
                  logger.warn("command is empty!");
                  return null;
              }
              String hostPort = MapUtils.getString(map, ClientReportConstant.COST_HOST_PORT, "");
              if (StringUtils.isBlank(hostPort)) {
                  logger.warn("hostPort is empty", hostPort);
                  return null;
              }
              int index = hostPort.indexOf(":");
              if (index <= 0) {
                  logger.warn("hostPort {} format is wrong", hostPort);
                  return null;
              }
              String host = hostPort.substring(0, index);
              int port = NumberUtils.toInt(hostPort.substring(index + 1));
  
              // 实例信息
              InstanceInfo instanceInfo = clientReportInstanceService.getInstanceInfoByHostPort(host, port);
              if (instanceInfo == null) {
  //                logger.warn("instanceInfo is empty, host is {}, port is {}", host, port);
                  return null;
              }
              long appId = instanceInfo.getAppId();
              // 耗时分布详情
              double mean = MapUtils.getDouble(map, ClientReportConstant.COST_TIME_MEAN, 0.0);
              Integer median = MapUtils.getInteger(map, ClientReportConstant.COST_TIME_MEDIAN, 0);
              Integer ninetyPercentMax = MapUtils.getInteger(map, ClientReportConstant.COST_TIME_90_MAX, 0);
              Integer ninetyNinePercentMax = MapUtils.getInteger(map, ClientReportConstant.COST_TIME_99_MAX, 0);
              Integer hunredMax = MapUtils.getInteger(map, ClientReportConstant.COST_TIME_100_MAX, 0);
  
              AppClientCostTimeStat stat = new AppClientCostTimeStat();
              stat.setAppId(appId);
              stat.setClientIp(clientIp);
              stat.setReportTime(new Date(reportTime));
              stat.setCollectTime(collectTime);
              stat.setCreateTime(new Date());
              stat.setCommand(command);
              stat.setCount(count);
              stat.setInstanceHost(host);
              stat.setInstancePort(port);
              stat.setMean(NumberUtils.toDouble(new DecimalFormat("#.00").format(mean)));
              stat.setMedian(median);
              stat.setNinetyPercentMax(ninetyPercentMax);
              stat.setNinetyNinePercentMax(ninetyNinePercentMax);
              stat.setHundredMax(hunredMax);
              stat.setInstanceId(instanceInfo.getId());
  
              return stat;
              
          } catch (Exception e) {
              logger.error(e.getMessage(), e);
              return null;
          }
      }
      
      /**
       * 1.获取最小的id
       * 2.获取date的id
       * 3.按照id批量删除
       */
      @Override
      public int deleteBeforeCollectTime(long collectTime) {
          long startTime = System.currentTimeMillis();
          int deleteCount = 0;
          try {
              int batchSize = 10000;
              long minId = appClientCostTimeStatDao.getTableMinimumId();
              long maxId = appClientCostTimeStatDao.getMinimumIdByCollectTime(collectTime);
              if (minId > maxId) {
                  return deleteCount;
              }
              long startId = minId;
              long endId = startId + batchSize;
              while (startId < maxId) {
                  if (endId > maxId) {
                      endId = maxId;
                  }
                  deleteCount += appClientCostTimeStatDao.deleteByIds(startId, endId);
                  startId += batchSize;
                  endId += batchSize;
              }
          } catch (Exception e) {
              logger.error(e.getMessage(), e);
          }
          logger.warn("batch delete before collectTime {} cost time is {} ms", collectTime, (System.currentTimeMillis() - startTime));
          return deleteCount;
      }
  
      public void setAppClientCostTimeStatDao(AppClientCostTimeStatDao appClientCostTimeStatDao) {
          this.appClientCostTimeStatDao = appClientCostTimeStatDao;
      }
  
      public void setClientReportInstanceService(ClientReportInstanceService clientReportInstanceService) {
          this.clientReportInstanceService = clientReportInstanceService;
      }
  
      public void setAppClientCostTimeTotalStatDao(AppClientCostTimeTotalStatDao appClientCostTimeTotalStatDao) {
          this.appClientCostTimeTotalStatDao = appClientCostTimeTotalStatDao;
      }
  
      public void setAppInstanceClientRelationService(AppInstanceClientRelationService appInstanceClientRelationService) {
          this.appInstanceClientRelationService = appInstanceClientRelationService;
      }
  
  
  }
  