Package com.prodeagle.java.servlets

Source Code of com.prodeagle.java.servlets.HarvestHandler

/*
* Copyright 2011 PA Consulting Ltd
*
* 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.prodeagle.java.servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.simple.JSONObject;

import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.apphosting.api.DeadlineExceededException;
import com.prodeagle.java.Authentication;
import com.prodeagle.java.MemCacheManager;
import com.prodeagle.java.ProdEagleConstants;
import com.prodeagle.java.counters.CounterNamesManager;
import com.prodeagle.java.counters.CounterUtil;

public class HarvestHandler extends HttpServlet implements ProdEagleConstants {
  /**
   * Auto-generated for Serializable
   */
  private static final long serialVersionUID = 7461496095351578024L;

  private static final Logger _logger = Logger.getLogger(HarvestHandler.class.getName());
 
  private static final UserService userService = UserServiceFactory.getUserService();
 
  private static final List<String> LOST_DATA_CHECK_KEYS = buildLostDataCheckKeys();
  private static final Map<String, Long> LOST_DATA_CHECK_VALUES = buildLostDataCheckValues();
 
  public void doGet(HttpServletRequest req, HttpServletResponse resp) {
    try {
      //check for the existence of ?viewer=xyz or ?administrator=xyz
      if (null != req.getParameter("viewer") || null != req.getParameter("administrator")) {
        _logger.info("Adding user");
        Authentication.addUser(req, resp);
      } else if (Authentication.isProdEagle(req, resp) || Authentication.isAdministrator(req, resp)) { //check prodeagle first, because admin does some redirecting
        String deleteCounter = req.getParameter("delete_counter");
        if (null != deleteCounter && !deleteCounter.isEmpty()) {
          _logger.info("Deleting counter: " + deleteCounter);
          CounterNamesManager cnm = CounterUtil.getDefaultCounterNamesManager();
          cnm.delete(Collections.singleton(deleteCounter));
        } else {
          _logger.info("Creating report");
          createReport(req, resp);
        }
      }
    } catch (DeadlineExceededException e) {
      _logger.severe("Unexpected DeadlineExceededException: " + e);
    } catch (Exception e) {
      _logger.severe("Unexpected exception: " + e);
    }
  }

  public void doPost(HttpServletRequest req, HttpServletResponse resp) {
    _logger.info(req.getParameterMap().toString());
   
    doGet(req, resp);
  }
 
  private Boolean isProductionCall(HttpServletRequest req) {
    String productionCallString = req.getParameter(PRODUCTION_CALL);
   
    Boolean isProductionCall = false;
    if (null != productionCallString && productionCallString.equalsIgnoreCase("1")) {
      isProductionCall = true;
    }
   
    _logger.info("Is production call? " + isProductionCall);
   
    return isProductionCall;
  }

  private long getLastSlot(HttpServletRequest req) {
    String lastSlotString = req.getParameter("last_time");
   
    Long lastSlot;
    if (null == lastSlotString) {
      _logger.info("Last slot not in parameters");
      long time = new Date().getTime() - MAX_LOOK_BACK;
      lastSlot = CounterUtil.getEpochRounded(new Date(time), MAX_LOOK_BACK);
    } else {
      _logger.info("Last slot in parameters");
      lastSlot = Long.parseLong(lastSlotString) * 1000; //turn it into milliseconds (because prodeagle gives it in seconds)
    }
   
    _logger.info("Last slot: " + lastSlot + " (or as a date: " + new Date(lastSlot).toString() + ")");
    return lastSlot;
  }
 
  @SuppressWarnings("unchecked")
  private void createReport(HttpServletRequest req, HttpServletResponse resp) {
    Boolean isProductionCall = isProductionCall(req);
   
    long slot = CounterUtil.getEpochRounded();
   
    JSONObject result = initaliseDefaultResult(slot);
   
    CounterNamesManager cnm = CounterUtil.getDefaultCounterNamesManager();
   
    Boolean allDataInaccurate = wasDataLostSinceLastHarvest(NAMESPACE, slot, isProductionCall);
   
    Set<String> allCounterNames = cnm.allCounterNames();
    Set<String> updateKeys = new HashSet<String>();
    Set<Object[]> updates = new HashSet<Object[]>();
   
    long lastSlot = getLastSlot(req);
    while (slot >= lastSlot) {
      long gap = new Date().getTime();
     
      Map<String, Object> slotUpdates = MemCacheManager.getMultipleCounters(allCounterNames, String.valueOf(slot), NAMESPACE);
           
      result.put("ms_of_data_lost", computeMsOfDataLost(gap, (Long) result.get("ms_of_data_lost")));
     
      updates.add(new Object[]{ slot, slotUpdates });
      updateKeys.addAll(slotUpdates.keySet());
     
      slot -= MIN_SLOT_SIZE;
    }

    if (isProductionCall) {
      MemCacheManager.deleteMulti(updateKeys, NAMESPACE);
    }
   
    if (!allDataInaccurate) {
      //have we lost any data since we first checked?
      allDataInaccurate = wasDataLostSinceLastHarvest(NAMESPACE, slot);
    }
   
    _logger.info("All data inaccurate? " + allDataInaccurate);
    //store the result of wasDataLost
    result.put("all_data_inaccurate", allDataInaccurate);
   
    buildCounterJSON(result, updateKeys, updates);

    writeReport(req, resp, isProductionCall, result);
  }

  @SuppressWarnings("unchecked")
  private void buildCounterJSON(JSONObject result, Set<String> updateKeys, Set<Object[]> updates) {
    try {
      for (String updateKey : updateKeys) {
        JSONObject counters = (JSONObject) result.get("counters");
 
        updateKey = updateKey.substring(updateKey.indexOf("_") + 1, updateKey.length());
        if (!counters.containsKey(updateKey)) {
          counters.put(updateKey, new JSONObject());
        }
       
        for (Object[] update : updates) {
          Long _slot = (Long) update[0];
          Map<String, Long> _slotUpdates = (Map<String, Long>) update[1];
         
          try {
            Object delta = _slotUpdates.get(_slot + "_" + updateKey);
           
            if (null != delta) {
              JSONObject x = (JSONObject) counters.get(updateKey);
              x.put(_slot / 1000, delta); //prodeagle time stamps are in seconds, not milliseconds
              counters.put(updateKey, x);
            }
          } catch (Exception e) {
            _logger.info("Test: " + e);
          }
        }
      }
    } catch (Exception e) {
      _logger.severe("Unexpected exception: " + e);
    }
   
    _logger.info("Creating counter JSON complete");
  }

  /**
   * Write the response out. If the request is for json / production
   * then write out the JSON string without anything else.
   *
   * If not json/production, write out a helpful list of the counters
   * and their values
   *
   * @param req - the request
   * @param resp - the response
   * @param isProductionCall - is this a call from ProdEagle.com?
   * @param result - the JSON to be written
   */
  private void writeReport(HttpServletRequest req, HttpServletResponse resp, Boolean isProductionCall, JSONObject result) {
    try {
      if (isProductionCall || null != req.getParameter("json")) {
        resp.setContentType("text/plain; charset=utf-8");
       
        String jsonString = result.toJSONString();
       
        resp.getWriter().print(jsonString);
      } else {
        resp.setContentType("text/html");
        PrintWriter writer = resp.getWriter();
        writer.print("<html><head><title>ProEagle Stats</title><style> td { padding: 5px }</style></head><body>");
        writer.print(String.format("<h3>Data since last export: %s UTC</h3>", new Date()));
        writer.print("<a href='http://www.prodeagle.com'>Go to ProdEagle dashboard</a>");
        writer.print(String.format("<br /><br /><a href='%s'>Logout</a>",
            userService.createLogoutURL(req.getRequestURI())));
       
        JSONObject counters = (JSONObject) result.get("counters");

        writer.print("<table><tr><td>Number of counters:</td><td>" + counters.size() + "</td></tr></table>");
       
        @SuppressWarnings("unchecked")
        List<String> sortedKeys = new ArrayList<String>(counters.keySet());
        Collections.sort(sortedKeys);
       
        for (Object counterKey : sortedKeys) {
          String counterName = (String) counterKey;
          JSONObject json = (JSONObject) counters.get(counterName);
         
          if (!json.isEmpty()) {
            writer.print("<table><thead><th colspan=\"3\">" + counterName + "</th></thead><tbody>");
           
            for (Object key : json.keySet()) {
              writer.print("<tr><td>");
              writer.print(key);
              writer.print("</td><td style=\"border-left: 1px solid grey; border-right: 1px solid grey;\">");
              writer.print(new Date((Long) key * 1000).toString()); //modify the dates back to include milliseconds
              writer.print("</td><td>");
              writer.print(json.get(key));
              writer.print("</td></tr>");
            }
            writer.print("</tbody></table>");
          }
        }
        writer.print("</body></html>");
        writer.flush();
      }
    } catch (IOException e) {
      _logger.severe("Failure to write response: " + e);
    }
   
    _logger.info("Writing Harvest report complete");
  }

  /**
   *
   * @param gap -
   * @param currentMsOfDataLost
   * @return
   */
  private long computeMsOfDataLost(long gap, long currentMsOfDataLost) {
    long thisGap = (new Date().getTime() - gap);
    long max = Math.max(thisGap, currentMsOfDataLost);
   
    return max;
  }

  private Boolean wasDataLostSinceLastHarvest(String namespace, long slot) {
    return wasDataLostSinceLastHarvest(namespace, slot, false);
  }
 
  private Boolean wasDataLostSinceLastHarvest(String namespace, long slot, Boolean isProductionCall) {
    _logger.info("Determining if data was lost since last harvest");
   
    //fetch, async, from Memcache values for those keys
    Future<Map<String, Object>> lostDataCheckFuture = MemCacheManager.getMultipleCounters(LOST_DATA_CHECK_KEYS, NAMESPACE);
   
    Future<Map<String, Long>> storeMultipleCountersResult = null;
   
    try {
      //actually wait for the results, no longer than 5 seconds - must be done before the deleteMulti call below.
      Map<String, Object> lostDataCheck = lostDataCheckFuture.get(5, TimeUnit.SECONDS);
     
      if (isProductionCall) {
        //if it's production, delete the existing keys from MemCache
        MemCacheManager.deleteMulti(LOST_DATA_CHECK_KEYS, NAMESPACE);
       
        //Store a Map of those keys, all initialised to a value of 1. This needs to be complete before the method exits though (see finally block)
        storeMultipleCountersResult = MemCacheManager.storeMultipleCounters(LOST_DATA_CHECK_VALUES, NAMESPACE, 0L);
      }

      if (lostDataCheck.values().size() != EXPECTED_MEMCACHE_SERVERS) {
        _logger.warning("ProdEagle counters lost before " + slot);
        return true;
      }
    } catch (Exception e) {
      _logger.warning("Exception while retrieving future from Memcache. Some metrics may be lost. Exception: " + e);
    } finally {
      if (null != storeMultipleCountersResult) { //i.e. not production
        try {
          //force a wait for the result of the storage of the new lost data counters
          storeMultipleCountersResult.get(10, TimeUnit.SECONDS);
        } catch (Exception e) {
          _logger.warning("Failed to store the new ProdEagle counters for checking of lost data. Some metrics may be lost in future calls. Exception: " + e);
        }
      }
    }
   
    _logger.info("Data was not lost before "+ slot);
    return false;
  }

  /**
   * Build a list of length N, where N is the EXPECTED_MEMCACHE_SERVERS count
   * which consists of a list of keys of the form "last_slot_0, last_slot_1 etc
   *
   * @return a List of length EXPECTED_MEMCACHE_SERVERS
   */
  private static List<String> buildLostDataCheckKeys() {
    //build an array of keys of the form "last_slot_x", where x is a number.
    List<String> lostDataCheckKeys = new ArrayList<String>(EXPECTED_MEMCACHE_SERVERS);
   
    int i = 0;
    while (i < EXPECTED_MEMCACHE_SERVERS) {
      lostDataCheckKeys.add("last_slot_" + i);
      i++;
    }
   
    return lostDataCheckKeys;
  }
 
  /**
   * Build a static Map of length N where N is the EXPECTED_MEMCACHE_SERVERS count
   * which consists of a list of keys of the form "last_slot_0, last_slot_1 etc" with
   * values 1
   *
   * @return a Map of length EXPECTED_MEMCACHE_SERVERS
   */
  private static Map<String, Long> buildLostDataCheckValues() {
    Map<String, Long> lostDataCheckValues = new HashMap<String, Long>(EXPECTED_MEMCACHE_SERVERS);
   
    for (String key : LOST_DATA_CHECK_KEYS) {
      lostDataCheckValues.put(key, 1L);
    }
   
    return lostDataCheckValues;
  }

  @SuppressWarnings("unchecked")
  /**
   * Creates a default result, with no results yet filled
   * @param slot - the epoch time
   * @return - a JSON Object with time, counters, ms_of_data_lost and version all set
   */
  private JSONObject initaliseDefaultResult(long slot) {
    JSONObject json = new JSONObject();
    json.put("time", slot / 1000); //turn into a python time (i.e. without milliseconds)
    json.put("counters", new JSONObject());
    json.put("ms_of_data_lost", 0L);
    json.put("version", 1.0);
   
    return json;
  }
}
TOP

Related Classes of com.prodeagle.java.servlets.HarvestHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.