/**
   * Listen to events that happen only on local node.
   *
   * @throws IgniteException If failed.
   */
  private static void localListen() throws IgniteException {
    System.out.println();
    System.out.println(">>> Local event listener example.");

    Ignite ignite = Ignition.ignite();

    IgnitePredicate<TaskEvent> lsnr =
        evt -> {
          System.out.println(
              "Received task event [evt=" + evt.name() + ", taskName=" + evt.taskName() + ']');

          return true; // Return true to continue listening.
        };

    // Register event listener for all local task execution events.
    ignite.events().localListen(lsnr, EVTS_TASK_EXECUTION);

    // Generate task events.
    ignite
        .compute()
        .withName("example-event-task")
        .run(() -> System.out.println("Executing sample job."));

    // Unsubscribe local task event listener.
    ignite.events().stopLocalListen(lsnr);
  }
  /** @throws Exception If failed. */
  public void testInternalTaskMetrics() throws Exception {
    Ignite ignite = grid();

    // Visor task is internal and should not affect metrics.
    ignite.compute().withName("visor-test-task").execute(new TestInternalTask(), "testArg");

    // Let metrics update twice.
    final CountDownLatch latch = new CountDownLatch(2);

    ignite
        .events()
        .localListen(
            new IgnitePredicate<Event>() {
              @Override
              public boolean apply(Event evt) {
                assert evt.type() == EVT_NODE_METRICS_UPDATED;

                latch.countDown();

                return true;
              }
            },
            EVT_NODE_METRICS_UPDATED);

    // Wait for metrics update.
    latch.await();

    ClusterMetrics metrics = ignite.cluster().localNode().metrics();

    info("Node metrics: " + metrics);

    assert metrics.getAverageActiveJobs() == 0;
    assert metrics.getAverageCancelledJobs() == 0;
    assert metrics.getAverageJobExecuteTime() == 0;
    assert metrics.getAverageJobWaitTime() == 0;
    assert metrics.getAverageRejectedJobs() == 0;
    assert metrics.getAverageWaitingJobs() == 0;
    assert metrics.getCurrentActiveJobs() == 0;
    assert metrics.getCurrentCancelledJobs() == 0;
    assert metrics.getCurrentJobExecuteTime() == 0;
    assert metrics.getCurrentJobWaitTime() == 0;
    assert metrics.getCurrentWaitingJobs() == 0;
    assert metrics.getMaximumActiveJobs() == 0;
    assert metrics.getMaximumCancelledJobs() == 0;
    assert metrics.getMaximumJobExecuteTime() == 0;
    assert metrics.getMaximumJobWaitTime() == 0;
    assert metrics.getMaximumRejectedJobs() == 0;
    assert metrics.getMaximumWaitingJobs() == 0;
    assert metrics.getTotalCancelledJobs() == 0;
    assert metrics.getTotalExecutedJobs() == 0;
    assert metrics.getTotalRejectedJobs() == 0;
    assert metrics.getTotalExecutedTasks() == 0;

    assertTrue(
        "MaximumJobExecuteTime="
            + metrics.getMaximumJobExecuteTime()
            + " is less than AverageJobExecuteTime="
            + metrics.getAverageJobExecuteTime(),
        metrics.getMaximumJobExecuteTime() >= metrics.getAverageJobExecuteTime());
  }
  /**
   * Listen to events coming from all cluster nodes.
   *
   * @throws IgniteException If failed.
   */
  private static void remoteListen() throws IgniteException {
    System.out.println();
    System.out.println(">>> Remote event listener example.");

    // This optional local callback is called for each event notification
    // that passed remote predicate listener.
    IgniteBiPredicate<UUID, TaskEvent> locLsnr =
        (nodeId, evt) -> {
          // Remote filter only accepts tasks whose name being with "good-task" prefix.
          assert evt.taskName().startsWith("good-task");

          System.out.println(
              "Received task event [evt=" + evt.name() + ", taskName=" + evt.taskName());

          return true; // Return true to continue listening.
        };

    // Remote filter which only accepts tasks whose name begins with "good-task" prefix.
    IgnitePredicate<TaskEvent> rmtLsnr = evt -> evt.taskName().startsWith("good-task");

    Ignite ignite = Ignition.ignite();

    // Register event listeners on all nodes to listen for task events.
    ignite.events().remoteListen(locLsnr, rmtLsnr, EVTS_TASK_EXECUTION);

    // Generate task events.
    for (int i = 0; i < 10; i++) {
      ignite
          .compute()
          .withName(i < 5 ? "good-task-" + i : "bad-task-" + i)
          .run(
              new IgniteRunnable() {
                // Auto-inject task session.
                @TaskSessionResource private ComputeTaskSession ses;

                @Override
                public void run() {
                  System.out.println("Executing sample job for task: " + ses.getTaskName());
                }
              });
    }
  }
  /**
   * Grabs local events and detects if events was lost since last poll.
   *
   * @param ignite Target grid.
   * @param evtOrderKey Unique key to take last order key from node local map.
   * @param evtThrottleCntrKey Unique key to take throttle count from node local map.
   * @param evtTypes Event types to collect.
   * @param evtMapper Closure to map grid events to Visor data transfer objects.
   * @return Collections of node events
   */
  public static Collection<VisorGridEvent> collectEvents(
      Ignite ignite,
      String evtOrderKey,
      String evtThrottleCntrKey,
      final int[] evtTypes,
      IgniteClosure<Event, VisorGridEvent> evtMapper) {
    assert ignite != null;
    assert evtTypes != null && evtTypes.length > 0;

    ConcurrentMap<String, Long> nl = ignite.cluster().nodeLocalMap();

    final long lastOrder = getOrElse(nl, evtOrderKey, -1L);
    final long throttle = getOrElse(nl, evtThrottleCntrKey, 0L);

    // When we first time arrive onto a node to get its local events,
    // we'll grab only last those events that not older than given period to make sure we are
    // not grabbing GBs of data accidentally.
    final long notOlderThan = System.currentTimeMillis() - EVENTS_COLLECT_TIME_WINDOW;

    // Flag for detecting gaps between events.
    final AtomicBoolean lastFound = new AtomicBoolean(lastOrder < 0);

    IgnitePredicate<Event> p =
        new IgnitePredicate<Event>() {
          /** */
          private static final long serialVersionUID = 0L;

          @Override
          public boolean apply(Event e) {
            // Detects that events were lost.
            if (!lastFound.get() && (lastOrder == e.localOrder())) lastFound.set(true);

            // Retains events by lastOrder, period and type.
            return e.localOrder() > lastOrder
                && e.timestamp() > notOlderThan
                && F.contains(evtTypes, e.type());
          }
        };

    Collection<Event> evts = ignite.events().localQuery(p);

    // Update latest order in node local, if not empty.
    if (!evts.isEmpty()) {
      Event maxEvt = Collections.max(evts, EVTS_ORDER_COMPARATOR);

      nl.put(evtOrderKey, maxEvt.localOrder());
    }

    // Update throttle counter.
    if (!lastFound.get())
      nl.put(evtThrottleCntrKey, throttle == 0 ? EVENTS_LOST_THROTTLE : throttle - 1);

    boolean lost = !lastFound.get() && throttle == 0;

    Collection<VisorGridEvent> res = new ArrayList<>(evts.size() + (lost ? 1 : 0));

    if (lost) res.add(new VisorGridEventsLost(ignite.cluster().localNode().id()));

    for (Event e : evts) {
      VisorGridEvent visorEvt = evtMapper.apply(e);

      if (visorEvt != null) res.add(visorEvt);
    }

    return res;
  }