alluxio-master-2.x/shell/src/test/java/alluxio/cli/fsadmin/report/JobServiceMetricsCommandTest.java
/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.cli.fsadmin.report;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import alluxio.client.job.JobMasterClient;
import alluxio.grpc.BuildVersion;
import alluxio.grpc.JobMasterStatus;
import alluxio.grpc.NetAddress;
import alluxio.job.wire.JobInfo;
import alluxio.job.wire.JobServiceSummary;
import alluxio.job.wire.JobWorkerHealth;
import alluxio.job.wire.PlanInfo;
import alluxio.job.wire.Status;

import com.google.common.collect.Lists;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

public class JobServiceMetricsCommandTest {

  private JobMasterClient mJobMasterClient;
  private ByteArrayOutputStream mOutputStream;
  private PrintStream mPrintStream;

  @Before
  public void before() throws IOException {
    mJobMasterClient = Mockito.mock(JobMasterClient.class);

    mOutputStream = new ByteArrayOutputStream();
    mPrintStream = new PrintStream(mOutputStream, true, "utf-8");
  }

  @After
  public void after() {
    mPrintStream.close();
  }

  @Test
  public void testBasic() throws IOException, ParseException {
    long now = Instant.now().toEpochMilli();
    String startTimeStr = JobServiceMetricsCommand.DATETIME_FORMAT
        .format(Instant.ofEpochMilli(now));
    JobMasterStatus primaryMaster = JobMasterStatus.newBuilder()
        .setMasterAddress(NetAddress.newBuilder()
            .setHost("master-node-1").setRpcPort(19998).build())
        .setState("PRIMARY").setStartTime(now).setVersion(BuildVersion.newBuilder()
            .setVersion("alluxio-version-2.9").setRevision("abcdef").build()).build();
    JobMasterStatus standbyMaster1 = JobMasterStatus.newBuilder()
        .setMasterAddress(NetAddress.newBuilder()
            .setHost("master-node-0").setRpcPort(19998).build())
        .setState("STANDBY").setStartTime(now).setVersion(
            BuildVersion.newBuilder().setVersion("alluxio-version-2.10")
                .setRevision("abcdef").build()).build();
    JobMasterStatus standbyMaster2 = JobMasterStatus.newBuilder()
        .setMasterAddress(NetAddress.newBuilder()
            .setHost("master-node-2").setRpcPort(19998).build())
        .setState("STANDBY").setStartTime(now).setVersion(
            BuildVersion.newBuilder().setVersion("alluxio-version-2.10")
                .setRevision("bcdefg").build()).build();
    Mockito.when(mJobMasterClient.getAllMasterStatus())
        .thenReturn(Lists.newArrayList(primaryMaster, standbyMaster1, standbyMaster2));

    JobWorkerHealth jobWorkerHealth = new JobWorkerHealth(
        1, Lists.newArrayList(1.2, 0.9, 0.7),
            10, 2, 2, "testHost",
            BuildVersion.newBuilder()
                .setVersion("2.10.0-SNAPSHOT").setRevision("ac6a0616").build());
    Mockito.when(mJobMasterClient.getAllWorkerHealth())
        .thenReturn(Lists.newArrayList(jobWorkerHealth));

    List<JobInfo> jobInfos = new ArrayList<>();

    jobInfos.add(createJobInfo(1, "Test1", Status.RUNNING, "2019-10-17 12:00:00"));
    jobInfos.add(createJobInfo(2, "Test2", Status.FAILED, "2019-10-17 12:30:15"));

    Mockito.when(mJobMasterClient.getJobServiceSummary())
            .thenReturn(new JobServiceSummary(jobInfos));

    new JobServiceMetricsCommand(mJobMasterClient, mPrintStream, "MM-dd-yyyy HH:mm:ss:SSS").run();

    String output = new String(mOutputStream.toByteArray(), StandardCharsets.UTF_8);

    String[] lineByLine = output.split("\n");

    // Master Status Section
    assertTrue(lineByLine[0].contains("Master Address      State    Start Time       "
        + "Version                          Revision"));
    assertTrue(lineByLine[1].contains("master-node-1:19998 PRIMARY"));
    assertTrue(lineByLine[1].contains(startTimeStr));
    assertTrue(lineByLine[1].contains("alluxio-version-2.9              abcdef"));
    assertTrue(lineByLine[2].contains("master-node-0:19998 STANDBY"));
    assertTrue(lineByLine[2].contains(startTimeStr));
    assertTrue(lineByLine[2].contains("alluxio-version-2.10             abcdef"));
    assertTrue(lineByLine[3].contains("master-node-2:19998 STANDBY"));
    assertTrue(lineByLine[3].contains(startTimeStr));
    assertTrue(lineByLine[3].contains("alluxio-version-2.10             bcdefg"));

    // Worker Health Section
    assertTrue(lineByLine[5].contains("Job Worker       Version                          "
        + "Revision Task Pool Size Unfinished Tasks Active Tasks Load Avg"));
    assertTrue(lineByLine[6].contains("testHost         2.10.0-SNAPSHOT                  "
        + "ac6a0616"));
    assertTrue(lineByLine[6].contains("10             2                2            "
        + "1.2, 0.9, 0.7"));

    // Group By Status
    lineByLine = ArrayUtils.subarray(lineByLine, 8, lineByLine.length);

    assertEquals("Status: CREATED   Count: 0", lineByLine[0]);
    assertEquals("Status: CANCELED  Count: 0", lineByLine[1]);
    assertEquals("Status: FAILED    Count: 1", lineByLine[2]);
    assertEquals("Status: RUNNING   Count: 1", lineByLine[3]);
    assertEquals("Status: COMPLETED Count: 0", lineByLine[4]);
    assertEquals("", lineByLine[5]);

    // Top 10
    lineByLine = ArrayUtils.subarray(lineByLine, 6, lineByLine.length);

    assertEquals("10 Most Recently Modified Jobs:", lineByLine[0]);
    assertEquals(
        "Timestamp: 01-17-2019 12:30:15:000       Id: 2                   Name: Test2"
        + "               Status: FAILED",
        lineByLine[1]);
    assertEquals(
        "Timestamp: 01-17-2019 12:00:00:000       Id: 1                   Name: Test1"
        + "               Status: RUNNING",
        lineByLine[2]);
    assertEquals("", lineByLine[3]);
    assertEquals("10 Most Recently Failed Jobs:", lineByLine[4]);
    assertEquals(
        "Timestamp: 01-17-2019 12:30:15:000       Id: 2                   Name: Test2"
        + "               Status: FAILED",
        lineByLine[5]);
    assertEquals("", lineByLine[6]);
    assertEquals("10 Longest Running Jobs:", lineByLine[7]);
    assertEquals(
        "Timestamp: 01-17-2019 12:00:00:000       Id: 1                   Name: Test1"
            + "               Status: RUNNING",
        lineByLine[8]);
  }

  private JobInfo createJobInfo(int id, String name, Status status, String datetime)
      throws ParseException {
    long timeMillis = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss").parse(datetime).getTime();
    PlanInfo jobInfo = new PlanInfo(id, name, status, timeMillis, null);
    return jobInfo;
  }
}
