/** {@inheritDoc} */
  @Override
  public void execute(@Nullable GridProjection prj) throws GridException {
    if (cb == null)
      throw new IllegalStateException("Mandatory local callback is not set for the query: " + this);

    if (prj == null) prj = ctx.grid();

    prj = prj.forCache(ctx.name());

    if (prj.nodes().isEmpty())
      throw new GridTopologyException("Failed to execute query (projection is empty): " + this);

    GridCacheMode mode = ctx.config().getCacheMode();

    if (mode == LOCAL || mode == REPLICATED) {
      Collection<GridNode> nodes = prj.nodes();

      GridNode node = nodes.contains(ctx.localNode()) ? ctx.localNode() : F.rand(nodes);

      assert node != null;

      if (nodes.size() > 1 && !ctx.cache().isDrSystemCache()) {
        if (node.id().equals(ctx.localNodeId()))
          U.warn(
              log,
              "Continuous query for "
                  + mode
                  + " cache can be run only on local node. "
                  + "Will execute query locally: "
                  + this);
        else
          U.warn(
              log,
              "Continuous query for "
                  + mode
                  + " cache can be run only on single node. "
                  + "Will execute query on remote node [qry="
                  + this
                  + ", node="
                  + node
                  + ']');
      }

      prj = prj.forNode(node);
    }

    closeLock.lock();

    try {
      if (routineId != null)
        throw new IllegalStateException("Continuous query can't be executed twice.");

      guard.block();

      GridContinuousHandler hnd =
          new GridCacheContinuousQueryHandler<>(ctx.name(), topic, cb, filter, prjPred);

      routineId =
          ctx.kernalContext()
              .continuous()
              .startRoutine(hnd, bufSize, timeInterval, autoUnsubscribe, prj.predicate())
              .get();
    } finally {
      closeLock.unlock();
    }
  }
  /** @param p Projection to get metrics for. */
  GridProjectionMetricsImpl(GridProjection p) {
    assert p != null;

    Collection<GridRichNode> nodes = p.nodes();

    int size = nodes.size();

    for (GridRichNode node : nodes) {
      GridNodeMetrics m = node.metrics();

      minActJobs = min(minActJobs, m.getCurrentActiveJobs());
      maxActJobs = max(maxActJobs, m.getCurrentActiveJobs());
      avgActJobs += m.getCurrentActiveJobs();

      minCancelJobs = min(minCancelJobs, m.getCurrentCancelledJobs());
      maxCancelJobs = max(maxCancelJobs, m.getCurrentCancelledJobs());
      avgCancelJobs += m.getCurrentCancelledJobs();

      minRejectJobs = min(minRejectJobs, m.getCurrentRejectedJobs());
      maxRejectJobs = max(maxRejectJobs, m.getCurrentRejectedJobs());
      avgRejectJobs += m.getCurrentRejectedJobs();

      minWaitJobs = min(minWaitJobs, m.getCurrentWaitingJobs());
      maxWaitJobs = max(maxWaitJobs, m.getCurrentWaitingJobs());
      avgWaitJobs += m.getCurrentWaitingJobs();

      minJobExecTime = min(minJobExecTime, m.getCurrentJobExecuteTime());
      maxJobExecTime = max(maxJobExecTime, m.getCurrentJobExecuteTime());
      avgJobExecTime += m.getCurrentJobExecuteTime();

      minJobWaitTime = min(minJobWaitTime, m.getCurrentJobWaitTime());
      maxJobWaitTime = max(maxJobWaitTime, m.getCurrentJobWaitTime());
      avgJobWaitTime += m.getCurrentJobWaitTime();

      minDaemonThreadCnt = min(minDaemonThreadCnt, m.getCurrentDaemonThreadCount());
      maxDaemonThreadCnt = max(maxDaemonThreadCnt, m.getCurrentDaemonThreadCount());
      avgDaemonThreadCnt += m.getCurrentDaemonThreadCount();

      minThreadCnt = min(minThreadCnt, m.getCurrentThreadCount());
      maxThreadCnt = max(maxThreadCnt, m.getCurrentThreadCount());
      avgThreadCnt += m.getCurrentThreadCount();

      minIdleTime = min(minIdleTime, m.getCurrentIdleTime());
      maxIdleTime = max(maxIdleTime, m.getCurrentIdleTime());
      avgIdleTime += m.getCurrentIdleTime();

      minBusyTimePerc = min(minBusyTimePerc, m.getBusyTimePercentage());
      maxBusyTimePerc = max(maxBusyTimePerc, m.getBusyTimePercentage());
      avgBusyTimePerc += m.getBusyTimePercentage();

      minCpuLoad = min(minCpuLoad, m.getCurrentCpuLoad());
      maxCpuLoad = max(maxCpuLoad, m.getCurrentCpuLoad());
      avgCpuLoad += m.getCurrentCpuLoad();

      minHeapMemCmt = min(minHeapMemCmt, m.getHeapMemoryCommitted());
      maxHeapMemCmt = max(maxHeapMemCmt, m.getHeapMemoryCommitted());
      avgHeapMemCmt += m.getHeapMemoryCommitted();

      minHeapMemUsed = min(minHeapMemUsed, m.getHeapMemoryUsed());
      maxHeapMemUsed = max(maxHeapMemUsed, m.getHeapMemoryUsed());
      avgHeapMemUsed += m.getHeapMemoryUsed();

      minHeapMemMax = min(minHeapMemMax, m.getHeapMemoryMaximum());
      maxHeapMemMax = max(maxHeapMemMax, m.getHeapMemoryMaximum());
      avgHeapMemMax += m.getHeapMemoryMaximum();

      minHeapMemInit = min(minHeapMemInit, m.getHeapMemoryInitialized());
      maxHeapMemInit = max(maxHeapMemInit, m.getHeapMemoryInitialized());
      avgHeapMemInit += m.getHeapMemoryInitialized();

      minNonHeapMemCmt = min(minNonHeapMemCmt, m.getNonHeapMemoryCommitted());
      maxNonHeapMemCmt = max(maxNonHeapMemCmt, m.getNonHeapMemoryCommitted());
      avgNonHeapMemCmt += m.getNonHeapMemoryCommitted();

      minNonHeapMemUsed = min(minNonHeapMemUsed, m.getNonHeapMemoryUsed());
      maxNonHeapMemUsed = max(maxNonHeapMemUsed, m.getNonHeapMemoryUsed());
      avgNonHeapMemUsed += m.getNonHeapMemoryUsed();

      minNonHeapMemMax = min(minNonHeapMemMax, m.getNonHeapMemoryMaximum());
      maxNonHeapMemMax = max(maxNonHeapMemMax, m.getNonHeapMemoryMaximum());
      avgNonHeapMemMax += m.getNonHeapMemoryMaximum();

      minNonHeapMemInit = min(minNonHeapMemInit, m.getNonHeapMemoryInitialized());
      maxNonHeapMemInit = max(maxNonHeapMemInit, m.getNonHeapMemoryInitialized());
      avgNonHeapMemInit += m.getNonHeapMemoryInitialized();

      minUpTime = min(minUpTime, m.getUpTime());
      maxUpTime = max(maxUpTime, m.getUpTime());
      avgUpTime += m.getUpTime();

      minCpusPerNode = min(minCpusPerNode, m.getTotalCpus());
      maxCpusPerNode = max(maxCpusPerNode, m.getTotalCpus());
      avgCpusPerNode += m.getTotalCpus();
    }

    avgActJobs /= size;
    avgCancelJobs /= size;
    avgRejectJobs /= size;
    avgWaitJobs /= size;
    avgJobExecTime /= size;
    avgJobWaitTime /= size;
    avgDaemonThreadCnt /= size;
    avgThreadCnt /= size;
    avgIdleTime /= size;
    avgBusyTimePerc /= size;
    avgCpuLoad /= size;
    avgHeapMemCmt /= size;
    avgHeapMemUsed /= size;
    avgHeapMemMax /= size;
    avgHeapMemInit /= size;
    avgNonHeapMemCmt /= size;
    avgNonHeapMemUsed /= size;
    avgNonHeapMemMax /= size;
    avgNonHeapMemInit /= size;
    avgUpTime /= size;
    avgCpusPerNode /= size;

    //
    // Note that since we are accessing projection
    // again it can get out of sync with previous
    // measurements as projection could have been
    // changed.
    //
    // We accept it for simplicity and performance
    // reasons. Metrics are not guaranteed to be
    // "transactional".
    //

    youngestNodeStartTime = p.youngest().metrics().getNodeStartTime();
    oldestNodeStartTime = p.oldest().metrics().getNodeStartTime();

    Collection<GridProjection> neighborhood = p.neighborhood();

    for (GridProjection neighbors : neighborhood) {
      minNodesPerHost = min(minNodesPerHost, neighbors.size());
      maxNodesPerHost = max(maxNodesPerHost, neighbors.size());
      avgNodesPerHost += neighbors.size();
    }

    avgNodesPerHost /= neighborhood.size();

    totalCpus = p.cpus();
    totalHosts = p.hosts();
    totalNodes = p.size();
  }