@Override
    public int compare(PersMethod theO1, PersMethod theO2) {
      BasePersServiceVersion v1 = theO1.getServiceVersion();
      BasePersServiceVersion v2 = theO2.getServiceVersion();
      PersService s1 = v1.getService();
      PersService s2 = v2.getService();
      PersDomain d1 = s1.getDomain();
      PersDomain d2 = s2.getDomain();

      int cmp = d1.getDomainId().compareTo(d2.getDomainId());
      if (cmp != 0) {
        return cmp;
      }

      cmp = s1.getServiceId().compareTo(s2.getServiceId());
      if (cmp != 0) {
        return cmp;
      }

      cmp = v1.getVersionId().compareTo(v2.getVersionId());
      if (cmp != 0) {
        return cmp;
      }

      cmp = theO1.getName().compareTo(theO2.getName());
      return cmp;
    }
  @Override
  public byte[] renderSvcVerUsageGraph(long theServiceVersionPid, TimeRange theRange)
      throws IOException {
    ourLog.info("Rendering latency graph for service version {}", theServiceVersionPid);

    myBroadcastSender.requestFlushQueuedStats();

    final List<Double> invCount = new ArrayList<>();
    final List<Double> invCountFault = new ArrayList<>();
    final List<Double> invCountFail = new ArrayList<>();
    final List<Double> invCountSecurityFail = new ArrayList<>();
    final List<Long> timestamps = new ArrayList<>();

    BasePersServiceVersion svcVer = myServiceRegistry.getServiceVersionByPid(theServiceVersionPid);
    for (PersMethod nextMethod : svcVer.getMethods()) {
      doWithStatsByMinute(
          myConfig.getConfig(),
          theRange,
          myStatus,
          nextMethod,
          new IWithStats<PersInvocationMethodSvcverStatsPk, PersInvocationMethodSvcverStats>() {
            @Override
            public void withStats(int theIndex, PersInvocationMethodSvcverStats theStats) {
              growToSizeDouble(invCount, theIndex);
              growToSizeDouble(invCountFault, theIndex);
              growToSizeDouble(invCountFail, theIndex);
              growToSizeDouble(invCountSecurityFail, theIndex);
              growToSizeLong(timestamps, theIndex);

              double numMinutes = theStats.getPk().getInterval().numMinutes();

              invCount.set(
                  theIndex,
                  invCount.get(theIndex) + (theStats.getSuccessInvocationCount() / numMinutes));
              invCountFault.set(
                  theIndex,
                  invCountFault.get(theIndex) + (theStats.getFaultInvocationCount() / numMinutes));
              invCountFail.set(
                  theIndex,
                  invCountFail.get(theIndex) + (theStats.getFailInvocationCount() / numMinutes));
              invCountSecurityFail.set(
                  theIndex,
                  invCountSecurityFail.get(theIndex)
                      + (theStats.getServerSecurityFailures() / numMinutes));
              timestamps.set(theIndex, theStats.getPk().getStartTime().getTime());
            }
          });
    }

    RrdGraphDef graphDef = new RrdGraphDef();
    graphDef.setWidth(600);
    graphDef.setHeight(200);
    graphDef.setTitle(
        "Usage: "
            + svcVer.getService().getDomain().getDomainId()
            + " / "
            + svcVer.getService().getServiceId()
            + " / "
            + svcVer.getVersionId());

    long[] timestamps1 = new long[invCount.size()];

    for (int i = 0; i < invCount.size(); i++) {
      timestamps1[i] = timestamps.get(i) / 1000;
    }

    graphDef.setTimeSpan(timestamps1);
    graphDef.setVerticalLabel("Calls / Min");
    graphDef.setTextAntiAliasing(true);

    LinearInterpolator avgPlot =
        new LinearInterpolator(timestamps1, toDoublesFromDoubles(invCount));
    graphDef.datasource("inv", avgPlot);
    if (hasValues(invCount)) {
      graphDef.area("inv", Color.decode("#00C000"), StringUtils.rightPad("Successful Calls", 20));
      graphDef.gprint("inv", ConsolFun.AVERAGE, "Average %8.1f   ");
      graphDef.gprint("inv", ConsolFun.MIN, "Min %8.1f   ");
      graphDef.gprint("inv", ConsolFun.MAX, "Max %8.1f\\l");
    } else {
      graphDef.area(
          "inv",
          Color.decode("#00C000"),
          StringUtils.rightPad("No Successful calls during this time period\\l", 20));
    }

    if (hasValues(invCountFault)) {
      LinearInterpolator avgFaultPlot =
          new LinearInterpolator(timestamps1, toDoublesFromDoubles(invCountFault));
      graphDef.datasource("invfault", avgFaultPlot);
      graphDef.stack("invfault", Color.decode("#6060C0"), StringUtils.rightPad("Faults", 20));
      graphDef.gprint("invfault", ConsolFun.AVERAGE, "Average %8.1f   ");
      graphDef.gprint("invfault", ConsolFun.MIN, "Min %8.1f   ");
      graphDef.gprint("invfault", ConsolFun.MAX, "Max %8.1f\\l");
    } else {
      graphDef.comment("No Faults during this time period\\l");
    }

    if (hasValues(invCountFail)) {
      LinearInterpolator avgFailPlot =
          new LinearInterpolator(timestamps1, toDoublesFromDoubles(invCountFail));
      graphDef.datasource("invfail", avgFailPlot);
      graphDef.stack("invfail", Color.decode("#F00000"), StringUtils.rightPad("Fails", 20));
      graphDef.gprint("invfail", ConsolFun.AVERAGE, "Average %8.1f   ");
      graphDef.gprint("invfail", ConsolFun.MIN, "Min %8.1f   ");
      graphDef.gprint("invfail", ConsolFun.MAX, "Max %8.1f\\l");
    } else {
      graphDef.comment("No Failures during this time period\\l");
    }

    if (hasValues(invCountSecurityFail)) {
      LinearInterpolator avgSecurityFailPlot =
          new LinearInterpolator(timestamps1, toDoublesFromDoubles(invCountSecurityFail));
      graphDef.datasource("invSecurityFail", avgSecurityFailPlot);
      graphDef.stack(
          "invSecurityFail", Color.decode("#F0A000"), StringUtils.rightPad("Security Fails", 20));
      graphDef.gprint("invSecurityFail", ConsolFun.AVERAGE, "Average %8.1f   ");
      graphDef.gprint("invSecurityFail", ConsolFun.MIN, "Min %8.1f   ");
      graphDef.gprint("invSecurityFail", ConsolFun.MAX, "Max %8.1f\\l");
    } else {
      graphDef.comment("No Security Failures during this time period\\l");
    }

    return render(graphDef);
  }
  @Override
  public byte[] renderSvcVerPayloadSizeGraph(long theServiceVersionPid, TimeRange theRange)
      throws IOException {
    ourLog.info("Rendering payload size graph for service version {}", theServiceVersionPid);

    myBroadcastSender.requestFlushQueuedStats();

    final List<Integer> invCount = new ArrayList<>();
    final List<Long> totalSuccessReqBytes = new ArrayList<>();
    final List<Long> totalSuccessRespBytes = new ArrayList<>();
    final List<Long> timestamps = new ArrayList<>();

    BasePersServiceVersion svcVer = myServiceRegistry.getServiceVersionByPid(theServiceVersionPid);
    for (PersMethod nextMethod : svcVer.getMethods()) {
      doWithStatsByMinute(
          myConfig.getConfig(),
          theRange,
          myStatus,
          nextMethod,
          new IWithStats<PersInvocationMethodSvcverStatsPk, PersInvocationMethodSvcverStats>() {
            @Override
            public void withStats(int theIndex, PersInvocationMethodSvcverStats theStats) {
              growToSizeInt(invCount, theIndex);
              growToSizeLong(totalSuccessRespBytes, theIndex);
              growToSizeLong(totalSuccessReqBytes, theIndex);
              growToSizeLong(timestamps, theIndex);

              totalSuccessReqBytes.set(
                  theIndex,
                  addToLong(
                      totalSuccessReqBytes.get(theIndex),
                      theStats.getSuccessRequestMessageBytes()));
              totalSuccessRespBytes.set(
                  theIndex,
                  addToLong(
                      totalSuccessRespBytes.get(theIndex),
                      theStats.getSuccessResponseMessageBytes()));
              invCount.set(
                  theIndex, addToInt(invCount.get(theIndex), theStats.getSuccessInvocationCount()));
              timestamps.set(theIndex, theStats.getPk().getStartTime().getTime());
            }
          });
    }

    double[] avgSuccessReqSize = new double[invCount.size()];
    double[] avgSuccessRespSize = new double[invCount.size()];
    for (int i = 0; i < invCount.size(); i++) {
      avgSuccessReqSize[i] =
          invCount.get(i) == 0 ? 0 : totalSuccessReqBytes.get(i) / invCount.get(i);
      avgSuccessRespSize[i] =
          invCount.get(i) == 0 ? 0 : totalSuccessRespBytes.get(i) / invCount.get(i);
    }

    RrdGraphDef graphDef = new RrdGraphDef();
    graphDef.setWidth(600);
    graphDef.setHeight(200);
    graphDef.setTitle(
        "Message Payload Size: "
            + svcVer.getService().getDomain().getDomainId()
            + " / "
            + svcVer.getService().getServiceId()
            + " / "
            + svcVer.getVersionId());

    long[] timestamps1 = new long[invCount.size()];

    double prevReq = 0.0;
    double prevResp = 0.0;
    for (int i = 0; i < invCount.size(); i++) {
      timestamps1[i] = timestamps.get(i) / 1000;
      prevReq = avgSuccessReqSize[i] > 0 ? avgSuccessReqSize[i] : prevReq;
      avgSuccessReqSize[i] = prevReq;
      prevResp = avgSuccessRespSize[i] > 0 ? avgSuccessRespSize[i] : prevResp;
      avgSuccessRespSize[i] = prevResp;
    }

    for (int i = 0; i < avgSuccessReqSize.length; i++) {}

    graphDef.setTimeSpan(timestamps1);
    graphDef.setVerticalLabel("Bytes / Message");
    graphDef.setTextAntiAliasing(true);

    LinearInterpolator reqPlot = new LinearInterpolator(timestamps1, avgSuccessReqSize);
    graphDef.datasource("req", reqPlot);
    graphDef.line("req", Color.RED, "Requests  ", 2.0f);
    graphDef.gprint("req", ConsolFun.AVERAGE, "Average Size %8.1f   ");
    graphDef.gprint("req", ConsolFun.MIN, "Min %8.1f   ");
    graphDef.gprint("req", ConsolFun.MAX, "Max %8.1f\\l");

    LinearInterpolator respPlot = new LinearInterpolator(timestamps1, avgSuccessRespSize);
    graphDef.datasource("resp", respPlot);
    graphDef.line("resp", Color.GREEN, "Responses ", 2.0f);
    graphDef.gprint("resp", ConsolFun.AVERAGE, "Average Size %8.1f   ");
    graphDef.gprint("resp", ConsolFun.MIN, "Min %8.1f   ");
    graphDef.gprint("resp", ConsolFun.MAX, "Max %8.1f\\l");

    return render(graphDef);
  }
  @Override
  public byte[] renderSvcVerThrottlingGraph(long theServiceVersionPid, TimeRange theRange)
      throws IOException {
    ourLog.info("Rendering throttling graph for service version {}", theServiceVersionPid);

    myBroadcastSender.requestFlushQueuedStats();

    final List<Long> throttleAcceptCount = new ArrayList<>();
    final List<Long> throttleRejectCount = new ArrayList<>();
    final List<Long> timestamps = new ArrayList<>();

    BasePersServiceVersion svcVer = myServiceRegistry.getServiceVersionByPid(theServiceVersionPid);
    for (PersMethod nextMethod : svcVer.getMethods()) {
      doWithStatsByMinute(
          myConfig.getConfig(),
          theRange,
          myStatus,
          nextMethod,
          new IWithStats<PersInvocationMethodSvcverStatsPk, PersInvocationMethodSvcverStats>() {
            @Override
            public void withStats(int theIndex, PersInvocationMethodSvcverStats theStats) {
              growToSizeLong(throttleAcceptCount, theIndex);
              growToSizeLong(throttleRejectCount, theIndex);
              growToSizeLong(timestamps, theIndex);

              double numMinutes = theStats.getPk().getInterval().numMinutes();

              throttleAcceptCount.set(
                  theIndex,
                  throttleAcceptCount.get(theIndex)
                      + (int) (theStats.getTotalThrottleAccepts() / numMinutes));
              throttleRejectCount.set(
                  theIndex,
                  throttleRejectCount.get(theIndex)
                      + (int) (theStats.getTotalThrottleRejections() / numMinutes));
              timestamps.set(theIndex, theStats.getPk().getStartTime().getTime());
            }
          });
    }

    RrdGraphDef graphDef = new RrdGraphDef();
    graphDef.setWidth(600);
    graphDef.setHeight(200);
    graphDef.setTitle(
        "Request Throttling: "
            + svcVer.getService().getDomain().getDomainId()
            + " / "
            + svcVer.getService().getServiceId()
            + " / "
            + svcVer.getVersionId());

    long[] timestamps1 = new long[timestamps.size()];

    for (int i = 0; i < timestamps.size(); i++) {
      timestamps1[i] = timestamps.get(i) / 1000;
    }

    graphDef.setTimeSpan(timestamps1);
    graphDef.setVerticalLabel("Throttled Calls / Min");
    graphDef.setTextAntiAliasing(true);

    LinearInterpolator avgPlot =
        new LinearInterpolator(timestamps1, toDoublesFromLongs(throttleAcceptCount));
    graphDef.datasource("inv", avgPlot);
    graphDef.area(
        "inv", Color.GREEN, "Accepted (Throttled but call was allowed to proceed after delay)\\l");
    graphDef.gprint("inv", ConsolFun.AVERAGE, "Average %8.1f   ");
    graphDef.gprint("inv", ConsolFun.MIN, "Min %8.1f   ");
    graphDef.gprint("inv", ConsolFun.MAX, "Max %8.1f\\l");

    LinearInterpolator avgFaultPlot =
        new LinearInterpolator(timestamps1, toDoublesFromLongs(throttleRejectCount));
    graphDef.datasource("invfault", avgFaultPlot);
    graphDef.stack("invfault", Color.BLUE, "Rejected (Throttle queue was full or not allowed)\\l");
    graphDef.gprint("invfault", ConsolFun.AVERAGE, "Average %8.1f   ");
    graphDef.gprint("invfault", ConsolFun.MIN, "Min %8.1f   ");
    graphDef.gprint("invfault", ConsolFun.MAX, "Max %8.1f\\l");

    return render(graphDef);
  }
  @Override
  public byte[] renderSvcVerLatencyMethodGraph(
      long theSvcVerPid, TimeRange theRange, boolean theIndividualMethod) throws IOException {
    ourLog.info("Rendering user method graph for Service Version {}", theSvcVerPid);

    myBroadcastSender.requestFlushQueuedStats();

    BasePersServiceVersion svcVer = myServiceRegistry.getServiceVersionByPid(theSvcVerPid);

    /*
     * Init the graph
     */

    RrdGraphDef graphDef = new RrdGraphDef();
    graphDef.setWidth(600);
    graphDef.setHeight(200);

    graphDef.setTitle(
        "Backing Service Latency: "
            + svcVer.getService().getDomain().getDomainId()
            + " / "
            + svcVer.getService().getServiceId()
            + " / "
            + svcVer.getVersionId());
    graphDef.setVerticalLabel("Milliseconds / Call");
    graphDef.setTextAntiAliasing(true);
    graphDef.setMinValue(0.0);

    /*
     * Loop through each method and load the latency stats
     */
    final List<String> names = new ArrayList<>();
    final List<List<Long>> latencyLists = new ArrayList<>();
    final List<Long> timestamps = new ArrayList<>();

    for (PersMethod nextMethod : svcVer.getMethods()) {
      if (BaseDtoServiceVersion.METHOD_NAME_UNKNOWN.equals(nextMethod.getName())) {
        continue;
      }

      final List<Long> latencyMin;
      if (theIndividualMethod) {
        names.add(nextMethod.getName());
        latencyMin = new ArrayList<>();
        latencyLists.add(latencyMin);
      } else {
        if (names.isEmpty()) {
          names.add("All Methods");
          latencyMin = new ArrayList<>();
          latencyLists.add(latencyMin);
        } else {
          latencyMin = latencyLists.get(0);
        }
      }

      doWithStatsByMinute(
          myConfig.getConfig(),
          theRange,
          myStatus,
          nextMethod,
          new IWithStats<PersInvocationMethodSvcverStatsPk, PersInvocationMethodSvcverStats>() {
            @Override
            public void withStats(int theIndex, PersInvocationMethodSvcverStats theStats) {
              growToSizeLong(latencyMin, theIndex);
              growToSizeLong(timestamps, theIndex);
              long latency =
                  theStats.getSuccessInvocationTotalTime() > 0
                      ? theStats.getSuccessInvocationTotalTime()
                          / theStats.getSuccessInvocationCount()
                      : 0;
              latencyMin.set(theIndex, addToLong(latencyMin.get(theIndex), latency));
              timestamps.set(theIndex, theStats.getPk().getStartTime().getTime());
            }
          });
    }

    /*
     * Set time span
     */
    long[] graphTimestamps = new long[timestamps.size()];
    for (int i = 0; i < timestamps.size(); i++) {
      graphTimestamps[i] = timestamps.get(i) / 1000;
    }
    graphDef.setTimeSpan(graphTimestamps);

    /*
     * Figure out the longest name
     */
    int longestName = 0;
    for (String next : names) {
      longestName = Math.max(longestName, next.length());
    }

    /*
     * Straighten
     */
    int numWithValues = 0;
    List<Boolean> hasValuesList = new ArrayList<>();
    for (List<Long> nextList : latencyLists) {
      boolean hasValues = false;
      for (int i = 0; i < nextList.size(); i++) {
        long l = nextList.get(i);
        if (l == 0) {
          if (i > 0 && nextList.get(i - 1) > 0) {
            nextList.set(i, nextList.get(i - 1));
          }
        } else {
          hasValues = true;
        }
      }
      hasValuesList.add(hasValues);
      if (hasValues) {
        numWithValues++;
      }
    }

    /*
     * Figure out colours
     */
    List<Color> colours = new ArrayList<>();
    int colourIndex = 0;
    for (int i = 0; i < hasValuesList.size(); i++) {
      if (hasValuesList.get(i)) {
        colours.add(createStackColour(numWithValues, colourIndex++));
      } else {
        colours.add(Color.black);
      }
    }

    /*
     * Add the lines to the graph
     */

    for (int i = 0; i < names.size(); i++) {
      String name = names.get(i);
      List<Long> latencyMin = latencyLists.get(i);

      Plottable avgPlot = new LinearInterpolator(graphTimestamps, toDoublesFromLongs(latencyMin));
      String srcName = "inv" + i;
      graphDef.datasource(srcName, avgPlot);

      graphDef.line(srcName, colours.get(i), " " + StringUtils.rightPad(name, longestName), 2);
      if (hasValuesList.get(i)) {
        graphDef.gprint(srcName, ConsolFun.AVERAGE, "Avg %5.1f ");
        graphDef.gprint(srcName, ConsolFun.MIN, "Min %5.1f ");
        graphDef.gprint(srcName, ConsolFun.MAX, "Max %5.1f \\l");
      } else {
        graphDef.comment("No Invocations During This Period\\l");
      }
    }

    return render(graphDef);
  }