/**
   * Runs basic cache example.
   *
   * @param args Command line arguments, none required.
   * @throws Exception If example execution failed.
   */
  public static void main(String[] args) throws Exception {
    final Grid g =
        args.length == 0 ? G.start("examples/config/spring-cache.xml") : G.start(args[0]);

    try {
      // Subscribe to events on every node, so we can visualize what's
      // happening in remote caches.
      g.run(
          BROADCAST,
          new CA() {
            @Override
            public void apply() {
              GridLocalEventListener lsnr =
                  new GridLocalEventListener() {
                    @Override
                    public void onEvent(GridEvent event) {
                      switch (event.type()) {
                        case EVT_CACHE_OBJECT_PUT:
                        case EVT_CACHE_OBJECT_READ:
                        case EVT_CACHE_OBJECT_REMOVED:
                          {
                            GridCacheEvent e = (GridCacheEvent) event;

                            X.println("Cache event [name=" + e.name() + ", key=" + e.key() + ']');
                          }
                      }
                    }
                  };

              GridNodeLocal<String, GridLocalEventListener> loc = g.nodeLocal();

              GridLocalEventListener prev = loc.remove("lsnr");

              // If there is a listener subscribed from previous runs, unsubscribe it.
              if (prev != null) g.removeLocalEventListener(prev);

              // Record new listener, so we can check it on next run.
              loc.put("lsnr", lsnr);

              // Subscribe listener.
              g.addLocalEventListener(lsnr, EVTS_CACHE);
            }
          });

      final GridCacheProjection<Integer, String> cache =
          g.cache(CACHE_NAME).projection(Integer.class, String.class);

      final int keyCnt = 20;

      // Store keys in cache.
      for (int i = 0; i < keyCnt; i++) cache.putx(i, Integer.toString(i));

      // Peek and get on local node.
      for (int i = 0; i < keyCnt; i++) {
        X.println("Peeked [key=" + i + ", val=" + cache.peek(i) + ']');
        X.println("Got [key=" + i + ", val=" + cache.get(i) + ']');
      }

      // Projection (view) for remote nodes.
      GridProjection rmts = g.remoteProjection();

      if (!rmts.isEmpty()) {
        // Peek and get on remote nodes (comment it out if output gets too crowded).
        g.remoteProjection()
            .run(
                BROADCAST,
                new GridAbsClosureX() {
                  @Override
                  public void applyx() throws GridException {
                    for (int i = 0; i < keyCnt; i++) {
                      X.println("Peeked [key=" + i + ", val=" + cache.peek(i) + ']');
                      X.println("Got [key=" + i + ", val=" + cache.get(i) + ']');
                    }
                  }
                });
      }
    } finally {
      G.stop(true);
    }
  }
  /** {@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();
  }