org.ngrinder.perftest.controller.PerfTestController.java Source code

Java tutorial

Introduction

Here is the source code for org.ngrinder.perftest.controller.PerfTestController.java

Source

/* 
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package org.ngrinder.perftest.controller;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import net.grinder.util.LogCompressUtils;
import net.grinder.util.Pair;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.mutable.MutableInt;
import org.ngrinder.agent.service.AgentManagerService;
import org.ngrinder.common.constant.ControllerConstants;
import org.ngrinder.common.controller.BaseController;
import org.ngrinder.common.controller.RestAPI;
import org.ngrinder.common.util.DateUtils;
import org.ngrinder.common.util.FileDownloadUtils;
import org.ngrinder.infra.config.Config;
import org.ngrinder.infra.logger.CoreLogger;
import org.ngrinder.infra.spring.RemainedPath;
import org.ngrinder.model.*;
import org.ngrinder.perftest.service.AgentManager;
import org.ngrinder.perftest.service.PerfTestService;
import org.ngrinder.perftest.service.TagService;
import org.ngrinder.region.service.RegionService;
import org.ngrinder.script.handler.ScriptHandlerFactory;
import org.ngrinder.script.model.FileCategory;
import org.ngrinder.script.model.FileEntry;
import org.ngrinder.script.service.FileEntryService;
import org.ngrinder.user.service.UserService;
import org.python.google.common.collect.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.web.PageableDefaults;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.net.URL;
import java.util.*;

import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayList;
import static org.apache.commons.lang.StringUtils.trimToEmpty;
import static org.ngrinder.common.util.CollectionUtils.buildMap;
import static org.ngrinder.common.util.CollectionUtils.newHashMap;
import static org.ngrinder.common.util.ExceptionUtils.processException;
import static org.ngrinder.common.util.ObjectUtils.defaultIfNull;
import static org.ngrinder.common.util.Preconditions.*;

/**
 * Performance Test Controller.
 *
 * @author Mavlarn
 * @author JunHo Yoon
 */
@SuppressWarnings("SpringJavaAutowiringInspection")
@Controller
@RequestMapping("/perftest")
public class PerfTestController extends BaseController {

    @Autowired
    private PerfTestService perfTestService;

    @Autowired
    private FileEntryService fileEntryService;

    @Autowired
    private AgentManager agentManager;

    @Autowired
    private AgentManagerService agentManagerService;

    @Autowired
    private TagService tagService;

    @Autowired
    private ScriptHandlerFactory scriptHandlerFactory;

    @Autowired
    private UserService userService;

    @Autowired
    private RegionService regionService;

    private Gson fileEntryGson;

    /**
     * Initialize.
     */
    @PostConstruct
    public void init() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(FileEntry.class, new FileEntry.FileEntrySerializer());
        fileEntryGson = gsonBuilder.create();
    }

    /**
     * Get the perf test lists.
     *
     * @param user        user
     * @param query       query string to search the perf test
     * @param tag         tag
     * @param queryFilter "F" means get only finished, "S" means get only scheduled tests.
     * @param pageable    page
     * @param model       modelMap
     * @return perftest/list
     */
    @RequestMapping({ "/list", "/", "" })
    public String getAll(User user, @RequestParam(required = false) String query,
            @RequestParam(required = false) String tag, @RequestParam(required = false) String queryFilter,
            @PageableDefaults Pageable pageable, ModelMap model) {
        pageable = new PageRequest(pageable.getPageNumber(), pageable.getPageSize(),
                defaultIfNull(pageable.getSort(), new Sort(Direction.DESC, "lastModifiedDate")));
        Page<PerfTest> tests = perfTestService.getPagedAll(user, query, tag, queryFilter, pageable);
        annotateDateMarker(tests);
        model.addAttribute("tag", tag);
        model.addAttribute("availTags", tagService.getAllTagStrings(user, StringUtils.EMPTY));
        model.addAttribute("testListPage", tests);
        model.addAttribute("queryFilter", queryFilter);
        model.addAttribute("query", query);
        putPageIntoModelMap(model, pageable);
        return "perftest/list";
    }

    private void annotateDateMarker(Page<PerfTest> tests) {
        TimeZone userTZ = TimeZone.getTimeZone(getCurrentUser().getTimeZone());
        Calendar userToday = Calendar.getInstance(userTZ);
        Calendar userYesterday = Calendar.getInstance(userTZ);
        userYesterday.add(Calendar.DATE, -1);
        for (PerfTest test : tests) {
            Calendar localedModified = Calendar.getInstance(userTZ);
            localedModified.setTime(
                    DateUtils.convertToUserDate(getCurrentUser().getTimeZone(), test.getLastModifiedDate()));
            if (org.apache.commons.lang.time.DateUtils.isSameDay(userToday, localedModified)) {
                test.setDateString("today");
            } else if (org.apache.commons.lang.time.DateUtils.isSameDay(userYesterday, localedModified)) {
                test.setDateString("yesterday");
            } else {
                test.setDateString("earlier");
            }
        }
    }

    /**
     * Open the new perf test creation form.
     *
     * @param user  user
     * @param model model
     * @return "perftest/detail"
     */
    @RequestMapping("/new")
    public String openForm(User user, ModelMap model) {
        return getOne(user, null, model);
    }

    /**
     * Get the perf test detail on the given perf test id.
     *
     * @param user  user
     * @param id    perf test id
     * @param model model
     * @return perftest/detail
     */
    @RequestMapping("/{id}")
    public String getOne(User user, @PathVariable("id") Long id, ModelMap model) {
        PerfTest test = null;
        if (id != null) {
            test = getOneWithPermissionCheck(user, id, true);
        }

        if (test == null) {
            test = new PerfTest(user);
            test.init();
        }

        model.addAttribute(PARAM_TEST, test);
        // Retrieve the agent count map based on create user, if the test is
        // created by the others.
        user = test.getCreatedUser() != null ? test.getCreatedUser() : user;

        Map<String, MutableInt> agentCountMap = agentManagerService.getAvailableAgentCountMap(user);
        model.addAttribute(PARAM_REGION_AGENT_COUNT_MAP, agentCountMap);
        model.addAttribute(PARAM_REGION_LIST, regionService.getAllVisibleRegionNames());
        model.addAttribute(PARAM_PROCESS_THREAD_POLICY_SCRIPT, perfTestService.getProcessAndThreadPolicyScript());
        addDefaultAttributeOnModel(model);
        return "perftest/detail";
    }

    private ArrayList<String> getRegions(Map<String, MutableInt> agentCountMap) {
        ArrayList<String> regions = new ArrayList<String>(agentCountMap.keySet());
        Collections.sort(regions);
        return regions;
    }

    /**
     * Search tags based on the given query.
     *
     * @param user  user to search
     * @param query query string
     * @return found tag list in json
     */
    @RequestMapping("/search_tag")
    public HttpEntity<String> searchTag(User user, @RequestParam(required = false) String query) {
        List<String> allStrings = tagService.getAllTagStrings(user, query);
        if (StringUtils.isNotBlank(query)) {
            allStrings.add(query);
        }
        return toJsonHttpEntity(allStrings);
    }

    /**
     * Add the various default configuration values on the model.
     *
     * @param model model to which put the default values
     */
    public void addDefaultAttributeOnModel(ModelMap model) {
        model.addAttribute(PARAM_AVAILABLE_RAMP_UP_TYPE, RampUp.values());
        model.addAttribute(PARAM_MAX_VUSER_PER_AGENT, agentManager.getMaxVuserPerAgent());
        model.addAttribute(PARAM_MAX_RUN_COUNT, agentManager.getMaxRunCount());
        model.addAttribute(PARAM_SECURITY_MODE, getConfig().isSecurityEnabled());
        model.addAttribute(PARAM_MAX_RUN_HOUR, agentManager.getMaxRunHour());
        model.addAttribute(PARAM_SAFE_FILE_DISTRIBUTION, getConfig().getControllerProperties()
                .getPropertyBoolean(ControllerConstants.PROP_CONTROLLER_SAFE_DIST));
        String timeZone = getCurrentUser().getTimeZone();
        int offset;
        if (StringUtils.isNotBlank(timeZone)) {
            offset = TimeZone.getTimeZone(timeZone).getOffset(System.currentTimeMillis());
        } else {
            offset = TimeZone.getDefault().getOffset(System.currentTimeMillis());
        }
        model.addAttribute(PARAM_TIMEZONE_OFFSET, offset);
    }

    /**
     * Get the perf test creation form for quickStart.
     *
     * @param user       user
     * @param urlString  URL string to be tested.
     * @param scriptType scriptType
     * @param model      model
     * @return perftest/detail
     */
    @RequestMapping("/quickstart")
    public String getQuickStart(User user, @RequestParam(value = "url", required = true) String urlString,
            @RequestParam(value = "scriptType", required = true) String scriptType,
            @RequestParam(value = "options", required = true) String options, ModelMap model) {
        URL url = checkValidURL(urlString);
        FileEntry newEntry = fileEntryService.prepareNewEntryForQuickTest(user, urlString,
                scriptHandlerFactory.getHandler(scriptType), options);
        model.addAttribute(PARAM_QUICK_SCRIPT, newEntry.getPath());
        model.addAttribute(PARAM_QUICK_SCRIPT_REVISION, newEntry.getRevision());
        model.addAttribute(PARAM_TEST,
                createPerfTestFromQuickStart(user, "Test for " + url.getHost(), url.getHost()));
        Map<String, MutableInt> agentCountMap = agentManagerService.getAvailableAgentCountMap(user);
        model.addAttribute(PARAM_REGION_AGENT_COUNT_MAP, agentCountMap);
        model.addAttribute(PARAM_REGION_LIST, getRegions(agentCountMap));
        addDefaultAttributeOnModel(model);
        model.addAttribute(PARAM_PROCESS_THREAD_POLICY_SCRIPT, perfTestService.getProcessAndThreadPolicyScript());
        return "perftest/detail";
    }

    /**
     * Create a new test from quick start mode.
     *
     * @param user       user
     * @param testName   test name
     * @param targetHost target host
     * @return test    {@link PerfTest}
     */
    private PerfTest createPerfTestFromQuickStart(User user, String testName, String targetHost) {
        PerfTest test = new PerfTest(user);
        test.init();
        test.setTestName(testName);
        test.setTargetHosts(targetHost);
        return test;
    }

    /**
     * Create a new test or cloneTo a current test.
     *
     * @param user     user
     * @param perfTest {@link PerfTest}
     * @param isClone  true if cloneTo
     * @param model    model
     * @return redirect:/perftest/list
     */
    @RequestMapping(value = "/new", method = RequestMethod.POST)
    public String saveOne(User user, PerfTest perfTest,
            @RequestParam(value = "isClone", required = false, defaultValue = "false") boolean isClone,
            ModelMap model) {

        validate(user, null, perfTest);
        // Point to the head revision
        perfTest.setTestName(StringUtils.trimToEmpty(perfTest.getTestName()));
        perfTest.setScriptRevision(-1L);
        perfTest.prepare(isClone);
        perfTest = perfTestService.save(user, perfTest);
        model.clear();
        if (perfTest.getStatus() == Status.SAVED || perfTest.getScheduledTime() != null) {
            return "redirect:/perftest/list";
        } else {
            return "redirect:/perftest/" + perfTest.getId();
        }
    }

    @SuppressWarnings("ConstantConditions")
    private void validate(User user, PerfTest oldOne, PerfTest newOne) {
        if (oldOne == null) {
            oldOne = new PerfTest();
            oldOne.init();
        }
        newOne = oldOne.merge(newOne);
        checkNotEmpty(newOne.getTestName(), "testName should be provided");
        checkArgument(newOne.getStatus().equals(Status.READY) || newOne.getStatus().equals(Status.SAVED),
                "status only allows SAVE or READY");
        if (newOne.isThresholdRunCount()) {
            final Integer runCount = newOne.getRunCount();
            checkArgument(runCount > 0 && runCount <= agentManager.getMaxRunCount(),
                    "runCount should be equal to or less than %s", agentManager.getMaxRunCount());
        } else {
            final Long duration = newOne.getDuration();
            checkArgument(duration > 0 && duration <= (((long) agentManager.getMaxRunHour()) * 3600000L),
                    "duration should be equal to or less than %s", agentManager.getMaxRunHour());
        }
        Map<String, MutableInt> agentCountMap = agentManagerService.getAvailableAgentCountMap(user);
        MutableInt agentCountObj = agentCountMap.get(isClustered() ? newOne.getRegion() : Config.NONE_REGION);
        checkNotNull(agentCountObj, "region should be within current region list");
        int agentMaxCount = agentCountObj.intValue();
        checkArgument(newOne.getAgentCount() <= agentMaxCount, "test agent should be equal to or less than %s",
                agentMaxCount);
        if (newOne.getStatus().equals(Status.READY)) {
            checkArgument(newOne.getAgentCount() >= 1, "agentCount should be more than 1 when it's READY status.");
        }

        checkArgument(newOne.getVuserPerAgent() <= agentManager.getMaxVuserPerAgent(),
                "vuserPerAgent should be equal to or less than %s", agentManager.getMaxVuserPerAgent());
        if (getConfig().isSecurityEnabled()) {
            checkArgument(StringUtils.isNotEmpty(newOne.getTargetHosts()),
                    "targetHosts should be provided when security mode is enabled");
        }
        if (newOne.getStatus() != Status.SAVED) {
            checkArgument(StringUtils.isNotBlank(newOne.getScriptName()), "scriptName should be provided.");
        }
        checkArgument(newOne.getVuserPerAgent() == newOne.getProcesses() * newOne.getThreads(),
                "vuserPerAgent should be equal to (processes * threads)");
    }

    /**
     * Leave the comment on the perf test.
     *
     * @param id          testId
     * @param user        user
     * @param testComment test comment
     * @param tagString   tagString
     * @return JSON
     */
    @RequestMapping(value = "/{id}/leave_comment", method = RequestMethod.POST)
    @ResponseBody
    public String leaveComment(User user, @PathVariable("id") Long id,
            @RequestParam("testComment") String testComment,
            @RequestParam(value = "tagString", required = false) String tagString) {
        perfTestService.addCommentOn(user, id, testComment, tagString);
        return returnSuccess();
    }

    private Long[] convertString2Long(String ids) {
        String[] numbers = StringUtils.split(ids, ",");
        Long[] id = new Long[numbers.length];
        int i = 0;
        for (String each : numbers) {
            id[i++] = NumberUtils.toLong(each, 0);
        }
        return id;
    }

    private List<Map<String, Object>> getStatus(List<PerfTest> perfTests) {
        List<Map<String, Object>> statuses = newArrayList();
        for (PerfTest each : perfTests) {
            Map<String, Object> result = newHashMap();
            result.put("id", each.getId());
            result.put("status_id", each.getStatus());
            result.put("status_type", each.getStatus());
            result.put("name", getMessages(each.getStatus().getSpringMessageKey()));
            result.put("icon", each.getStatus().getIconName());
            result.put("message", StringUtils.replace(each.getProgressMessage() + "\n<b>"
                    + each.getLastProgressMessage() + "</b>\n" + each.getLastModifiedDateToStr(), "\n", "<br/>"));
            result.put("deletable", each.getStatus().isDeletable());
            result.put("stoppable", each.getStatus().isStoppable());
            result.put("reportable", each.getStatus().isReportable());
            statuses.add(result);
        }
        return statuses;
    }

    /**
     * Delete the perf tests having given IDs.
     *
     * @param user user
     * @param ids  comma operated IDs
     * @return success json messages if succeeded.
     */
    @RestAPI
    @RequestMapping(value = "/api", method = RequestMethod.DELETE)
    public HttpEntity<String> delete(User user, @RequestParam(value = "ids", defaultValue = "") String ids) {
        for (String idStr : StringUtils.split(ids, ",")) {
            perfTestService.delete(user, NumberUtils.toLong(idStr, 0));
        }
        return successJsonHttpEntity();
    }

    /**
     * Stop the perf tests having given IDs.
     *
     * @param user user
     * @param ids  comma separated perf test IDs
     * @return success json if succeeded.
     */
    @RestAPI
    @RequestMapping(value = "/api", params = "action=stop", method = RequestMethod.PUT)
    public HttpEntity<String> stop(User user, @RequestParam(value = "ids", defaultValue = "") String ids) {
        for (String idStr : StringUtils.split(ids, ",")) {
            perfTestService.stop(user, NumberUtils.toLong(idStr, 0));
        }
        return successJsonHttpEntity();
    }

    /**
     * Filter out please_modify_this.com from hosts string.
     *
     * @param originalString original string
     * @return filtered string
     */
    private String filterHostString(String originalString) {
        List<String> hosts = newArrayList();
        for (String each : StringUtils.split(StringUtils.trimToEmpty(originalString), ",")) {
            if (!each.contains("please_modify_this.com")) {
                hosts.add(each);
            }
        }
        return StringUtils.join(hosts, ",");
    }

    private Map<String, Object> getPerfGraphData(Long id, String[] dataTypes, boolean onlyTotal, int imgWidth) {
        final PerfTest test = perfTestService.getOne(id);
        int interval = perfTestService.getReportDataInterval(id, dataTypes[0], imgWidth);
        Map<String, Object> resultMap = Maps.newHashMap();
        for (String each : dataTypes) {
            Pair<ArrayList<String>, ArrayList<String>> tpsResult = perfTestService.getReportData(id, each,
                    onlyTotal, interval);
            Map<String, Object> dataMap = Maps.newHashMap();
            dataMap.put("labels", tpsResult.getFirst());
            dataMap.put("data", tpsResult.getSecond());
            resultMap.put(StringUtils.replaceChars(each, "()", ""), dataMap);
        }
        resultMap.put(PARAM_TEST_CHART_INTERVAL, interval * test.getSamplingInterval());
        return resultMap;
    }

    /**
     * Get the running division in perftest configuration page.
     *
     * @param user  user
     * @param model model
     * @param id    test id
     * @return perftest/running
     */
    @RequestMapping(value = "{id}/running_div")
    public String getReportSection(User user, ModelMap model, @PathVariable long id) {
        PerfTest test = getOneWithPermissionCheck(user, id, false);
        model.addAttribute(PARAM_TEST, test);
        return "perftest/running";
    }

    /**
     * Get the basic report content in perftest configuration page.
     * <p/>
     * This method returns the appropriate points based on the given imgWidth.
     *
     * @param user     user
     * @param model    model
     * @param id       test id
     * @param imgWidth image width
     * @return perftest/basic_report
     */
    @RequestMapping(value = "{id}/basic_report")
    public String getReportSection(User user, ModelMap model, @PathVariable long id, @RequestParam int imgWidth) {
        PerfTest test = getOneWithPermissionCheck(user, id, false);
        int interval = perfTestService.getReportDataInterval(id, "TPS", imgWidth);
        model.addAttribute(PARAM_LOG_LIST, perfTestService.getLogFiles(id));
        model.addAttribute(PARAM_TEST_CHART_INTERVAL, interval * test.getSamplingInterval());
        model.addAttribute(PARAM_TEST, test);
        model.addAttribute(PARAM_TPS, perfTestService.getSingleReportDataAsJson(id, "TPS", interval));
        return "perftest/basic_report";
    }

    /**
     * Download the CSV report for the given perf test id.
     *
     * @param user     user
     * @param id       test id
     * @param response response
     */
    @RequestMapping(value = "/{id}/download_csv")
    public void downloadCSV(User user, @PathVariable("id") long id, HttpServletResponse response) {
        PerfTest test = getOneWithPermissionCheck(user, id, false);
        File targetFile = perfTestService.getCsvReportFile(test);
        checkState(targetFile.exists(), "File %s doesn't exist!", targetFile.getName());
        FileDownloadUtils.downloadFile(response, targetFile);
    }

    /**
     * Download logs for the perf test having the given id.
     *
     * @param user     user
     * @param id       test id
     * @param path     path in the log folder
     * @param response response
     */
    @RequestMapping(value = "/{id}/download_log/**")
    public void downloadLog(User user, @PathVariable("id") long id, @RemainedPath String path,
            HttpServletResponse response) {
        getOneWithPermissionCheck(user, id, false);
        File targetFile = perfTestService.getLogFile(id, path);
        FileDownloadUtils.downloadFile(response, targetFile);
    }

    /**
     * Show the given log for the perf test having the given id.
     *
     * @param user     user
     * @param id       test id
     * @param path     path in the log folder
     * @param response response
     */
    @RequestMapping(value = "/{id}/show_log/**")
    public void showLog(User user, @PathVariable("id") long id, @RemainedPath String path,
            HttpServletResponse response) {
        getOneWithPermissionCheck(user, id, false);
        File targetFile = perfTestService.getLogFile(id, path);
        response.reset();
        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(targetFile);
            ServletOutputStream outputStream = response.getOutputStream();
            if (FilenameUtils.isExtension(targetFile.getName(), "zip")) {
                // Limit log view to 1MB
                outputStream.println(" Only the last 1MB of a log shows.\n");
                outputStream
                        .println("==========================================================================\n\n");
                LogCompressUtils.decompress(fileInputStream, outputStream, 1 * 1024 * 1204);
            } else {
                IOUtils.copy(fileInputStream, outputStream);
            }
        } catch (Exception e) {
            CoreLogger.LOGGER.error("Error while processing log. {}", targetFile, e);
        } finally {
            IOUtils.closeQuietly(fileInputStream);
        }
    }

    /**
     * Get the running perf test info having the given id.
     *
     * @param user user
     * @param id   test id
     * @return JSON message   containing test,agent and monitor status.
     */
    @RequestMapping(value = "/{id}/api/sample")
    @RestAPI
    public HttpEntity<String> refreshTestRunning(User user, @PathVariable("id") long id) {
        PerfTest test = checkNotNull(getOneWithPermissionCheck(user, id, false),
                "given test should be exist : " + id);
        Map<String, Object> map = newHashMap();
        map.put("status", test.getStatus());
        map.put("perf", perfTestService.getStatistics(test));
        map.put("agent", perfTestService.getAgentStat(test));
        map.put("monitor", perfTestService.getMonitorStat(test));
        return toJsonHttpEntity(map);
    }

    /**
     * Get the detailed perf test report.
     *
     * @param model model
     * @param id    test id
     * @return perftest/detail_report
     */
    @SuppressWarnings("MVCPathVariableInspection")
    @RequestMapping(value = { "/{id}/detail_report", /** for backward compatibility */
            "/{id}/report" })
    public String getReport(ModelMap model, @PathVariable("id") long id) {
        model.addAttribute("test", perfTestService.getOne(id));
        model.addAttribute("plugins", perfTestService.getAvailableReportPlugins(id));
        return "perftest/detail_report";
    }

    /**
     * Get the detailed perf test report.
     *
     * @param id test id
     * @return perftest/detail_report/perf
     */
    @SuppressWarnings({ "MVCPathVariableInspection", "UnusedParameters" })
    @RequestMapping("/{id}/detail_report/perf")
    public String getDetailPerfReport(@PathVariable("id") long id) {
        return "perftest/detail_report/perf";
    }

    /**
     * Get the detailed perf test monitor report.
     *
     * @param id       test id
     * @param targetIP target ip
     * @param modelMap model map
     * @return perftest/detail_report/monitor
     */
    @SuppressWarnings("UnusedParameters")
    @RequestMapping("/{id}/detail_report/monitor")
    public String getDetailMonitorReport(@PathVariable("id") long id, @RequestParam("targetIP") String targetIP,
            ModelMap modelMap) {
        modelMap.addAttribute("targetIP", targetIP);
        return "perftest/detail_report/monitor";
    }

    /**
     * Get the detailed perf test report.
     *
     * @param id       test id
     * @param plugin   test report plugin category
     * @param modelMap model map
     * @return perftest/detail_report/plugin
     */
    @SuppressWarnings("UnusedParameters")
    @RequestMapping("/{id}/detail_report/plugin/{plugin}")
    public String getDetailPluginReport(@PathVariable("id") long id, @PathVariable("plugin") String plugin,
            @RequestParam("kind") String kind, ModelMap modelMap) {
        modelMap.addAttribute("plugin", plugin);
        modelMap.addAttribute("kind", kind);
        return "perftest/detail_report/plugin";
    }

    private PerfTest getOneWithPermissionCheck(User user, Long id, boolean withTag) {
        PerfTest perfTest = withTag ? perfTestService.getOneWithTag(id) : perfTestService.getOne(id);
        if (user.getRole().equals(Role.ADMIN) || user.getRole().equals(Role.SUPER_USER)) {
            return perfTest;
        }
        if (perfTest != null && !user.equals(perfTest.getCreatedUser())) {
            throw processException("User " + user.getUserId() + " has no right on PerfTest " + id);
        }
        return perfTest;
    }

    private Map<String, String> getMonitorGraphData(long id, String targetIP, int imgWidth) {
        int interval = perfTestService.getMonitorGraphInterval(id, targetIP, imgWidth);
        Map<String, String> sysMonitorMap = perfTestService.getMonitorGraph(id, targetIP, interval);
        PerfTest perfTest = perfTestService.getOne(id);
        sysMonitorMap.put("interval",
                String.valueOf(interval * (perfTest != null ? perfTest.getSamplingInterval() : 1)));
        return sysMonitorMap;
    }

    /**
     * Get the count of currently running perf test and the detailed progress info for the given perf test IDs.
     *
     * @param user user
     * @param ids  comma separated perf test list
     * @return JSON message containing perf test status
     */
    @RestAPI
    @RequestMapping("/api/status")
    public HttpEntity<String> getStatuses(User user, @RequestParam(value = "ids", defaultValue = "") String ids) {
        List<PerfTest> perfTests = perfTestService.getAll(user, convertString2Long(ids));
        return toJsonHttpEntity(buildMap("perfTestInfo", perfTestService.getCurrentPerfTestStatistics(), "status",
                getStatus(perfTests)));
    }

    /**
     * Get all available scripts in JSON format for the current factual user.
     *
     * @param user    user
     * @param ownerId owner id
     * @return JSON containing script's list.
     */
    @RestAPI
    @RequestMapping("/api/script")
    public HttpEntity<String> getScripts(User user,
            @RequestParam(value = "ownerId", required = false) String ownerId) {
        if (StringUtils.isNotEmpty(ownerId)) {
            user = userService.getOne(ownerId);
        }
        List<FileEntry> scripts = newArrayList(
                filter(fileEntryService.getAll(user), new com.google.common.base.Predicate<FileEntry>() {
                    @Override
                    public boolean apply(@Nullable FileEntry input) {
                        return input != null && input.getFileType().getFileCategory() == FileCategory.SCRIPT;
                    }
                }));
        return toJsonHttpEntity(scripts, fileEntryGson);
    }

    /**
     * Get resources and lib file list from the same folder with the given script path.
     *
     * @param user       user
     * @param scriptPath script path
     * @param ownerId    ownerId
     * @return json string representing resources and libs.
     */
    @RequestMapping("/api/resource")
    public HttpEntity<String> getResources(User user, @RequestParam String scriptPath,
            @RequestParam(required = false) String ownerId) {
        if (user.getRole() == Role.ADMIN && StringUtils.isNotBlank(ownerId)) {
            user = userService.getOne(ownerId);
        }
        FileEntry fileEntry = fileEntryService.getOne(user, scriptPath);
        String targetHosts = "";
        List<String> fileStringList = newArrayList();
        if (fileEntry != null) {
            List<FileEntry> fileList = fileEntryService.getScriptHandler(fileEntry).getLibAndResourceEntries(user,
                    fileEntry, -1L);
            for (FileEntry each : fileList) {
                fileStringList.add(each.getPath());
            }
            targetHosts = filterHostString(fileEntry.getProperties().get("targetHosts"));
        }

        return toJsonHttpEntity(buildMap("targetHosts", trimToEmpty(targetHosts), "resources", fileStringList));
    }

    /**
     * Get the status of the given perf test.
     *
     * @param user user
     * @param id   perftest id
     * @return JSON message containing perf test status
     */
    @RestAPI
    @RequestMapping("/api/{id}/status")
    public HttpEntity<String> getStatus(User user, @PathVariable("id") Long id) {
        List<PerfTest> perfTests = perfTestService.getAll(user, new Long[] { id });
        return toJsonHttpEntity(buildMap("status", getStatus(perfTests)));
    }

    /**
     * Get the logs of the given perf test.
     *
     * @param user user
     * @param id   perftest id
     * @return JSON message containing log file names
     */
    @RestAPI
    @RequestMapping("/api/{id}/logs")
    public HttpEntity<String> getLogs(User user, @PathVariable("id") Long id) {
        // Check permission
        getOneWithPermissionCheck(user, id, false);
        return toJsonHttpEntity(perfTestService.getLogFiles(id));
    }

    /**
     * Get the detailed report graph data for the given perf test id.
     * This method returns the appropriate points based on the given imgWidth.
     *
     * @param id       test id
     * @param dataType which data
     * @param imgWidth imageWidth
     * @return json string.
     */
    @SuppressWarnings("MVCPathVariableInspection")
    @RestAPI
    @RequestMapping({ "/api/{id}/perf", "/api/{id}/graph" })
    public HttpEntity<String> getPerfGraph(@PathVariable("id") long id,
            @RequestParam(required = true, defaultValue = "") String dataType,
            @RequestParam(defaultValue = "false") boolean onlyTotal, @RequestParam int imgWidth) {
        String[] dataTypes = checkNotEmpty(StringUtils.split(dataType, ","),
                "dataType argument should be provided");
        return toJsonHttpEntity(getPerfGraphData(id, dataTypes, onlyTotal, imgWidth));
    }

    /**
     * Get the monitor data of the target having the given IP.
     *
     * @param id       test Id
     * @param targetIP targetIP
     * @param imgWidth image width
     * @return json message
     */
    @RestAPI
    @RequestMapping("/api/{id}/monitor")
    public HttpEntity<String> getMonitorGraph(@PathVariable("id") long id,
            @RequestParam("targetIP") String targetIP, @RequestParam int imgWidth) {
        return toJsonHttpEntity(getMonitorGraphData(id, targetIP, imgWidth));
    }

    /**
     * Get the plugin monitor data of the target.
     *
     * @param id       test Id
     * @param plugin   monitor plugin category
     * @param kind     kind
     * @param imgWidth image width
     * @return json message
     */
    @RestAPI
    @RequestMapping("/api/{id}/plugin/{plugin}")
    public HttpEntity<String> getPluginGraph(@PathVariable("id") long id, @PathVariable("plugin") String plugin,
            @RequestParam("kind") String kind, @RequestParam int imgWidth) {
        return toJsonHttpEntity(getReportPluginGraphData(id, plugin, kind, imgWidth));
    }

    private Map<String, Object> getReportPluginGraphData(long id, String plugin, String kind, int imgWidth) {
        int interval = perfTestService.getReportPluginGraphInterval(id, plugin, kind, imgWidth);
        Map<String, Object> pluginMonitorData = perfTestService.getReportPluginGraph(id, plugin, kind, interval);
        final PerfTest perfTest = perfTestService.getOne(id);
        int samplingInterval = 3;
        if (perfTest != null) {
            samplingInterval = perfTest.getSamplingInterval();
        }
        pluginMonitorData.put("interval", interval * samplingInterval);
        return pluginMonitorData;
    }

    /**
     * Get the last perf test details in the form of json.
     *
     * @param user user
     * @param page page
     * @param size size of retrieved perf test
     * @return json string
     */
    @RestAPI
    @RequestMapping(value = { "/api/last", "/api", "/api/" }, method = RequestMethod.GET)
    public HttpEntity<String> getAll(User user, @RequestParam(value = "page", defaultValue = "0") int page,
            @RequestParam(value = "size", defaultValue = "1") int size) {
        PageRequest pageRequest = new PageRequest(page, size, new Sort(Direction.DESC, "id"));
        Page<PerfTest> testList = perfTestService.getPagedAll(user, null, null, null, pageRequest);
        return toJsonHttpEntity(testList.getContent());
    }

    /**
     * Get the perf test detail in the form of json.
     *
     * @param user user
     * @param id   perftest id
     * @return json message containing test info.
     */
    @RestAPI
    @RequestMapping(value = "/api/{id}", method = RequestMethod.GET)
    public HttpEntity<String> getOne(User user, @PathVariable("id") Long id) {
        PerfTest test = checkNotNull(getOneWithPermissionCheck(user, id, false), "PerfTest %s does not exists", id);
        return toJsonHttpEntity(test);
    }

    /**
     * Create the given perf test.
     *
     * @param user     user
     * @param perfTest perf test
     * @return json message containing test info.
     */
    @RestAPI
    @RequestMapping(value = { "/api/", "/api" }, method = RequestMethod.POST)
    public HttpEntity<String> create(User user, PerfTest perfTest) {
        checkNull(perfTest.getId(), "id should be null");
        validate(user, null, perfTest);
        PerfTest savePerfTest = perfTestService.save(user, perfTest);
        return toJsonHttpEntity(savePerfTest);
    }

    /**
     * Delete the given perf test.
     *
     * @param user user
     * @param id   perf test id
     * @return json success message if succeeded
     */
    @RestAPI
    @RequestMapping(value = "/api/{id}", method = RequestMethod.DELETE)
    public HttpEntity<String> delete(User user, @PathVariable("id") Long id) {
        PerfTest perfTest = getOneWithPermissionCheck(user, id, false);
        checkNotNull(perfTest, "no perftest for %s exits", id);
        perfTestService.delete(user, id);
        return successJsonHttpEntity();
    }

    /**
     * Update the given perf test.
     *
     * @param user     user
     * @param id       perf test id
     * @param perfTest perf test configuration changes
     * @return json message
     */
    @RestAPI
    @RequestMapping(value = "/api/{id}", method = RequestMethod.PUT)
    public HttpEntity<String> update(User user, @PathVariable("id") Long id, PerfTest perfTest) {
        PerfTest existingPerfTest = getOneWithPermissionCheck(user, id, false);
        perfTest.setId(id);
        validate(user, existingPerfTest, perfTest);
        return toJsonHttpEntity(perfTestService.save(user, perfTest));
    }

    /**
     * Stop the given perf test.
     *
     * @param user user
     * @param id   perf test id
     * @return json success message if succeeded
     */
    @RestAPI
    @RequestMapping(value = "/api/{id}", params = "action=stop", method = RequestMethod.PUT)
    public HttpEntity<String> stop(User user, @PathVariable("id") Long id) {
        perfTestService.stop(user, id);
        return successJsonHttpEntity();
    }

    /**
     * Update the given perf test's status.
     *
     * @param user   user
     * @param id     perf test id
     * @param status Status to be moved to
     * @return json message
     */
    @RestAPI
    @RequestMapping(value = "/api/{id}", params = "action=status", method = RequestMethod.PUT)
    public HttpEntity<String> updateStatus(User user, @PathVariable("id") Long id, Status status) {
        PerfTest perfTest = getOneWithPermissionCheck(user, id, false);
        checkNotNull(perfTest, "no perftest for %s exits", id).setStatus(status);
        validate(user, null, perfTest);
        return toJsonHttpEntity(perfTestService.save(user, perfTest));
    }

    /**
     * Clone and start the given perf test.
     *
     * @param user     user
     * @param id       perf test id to be cloned
     * @param perftest option to override while cloning.
     * @return json string
     */
    @SuppressWarnings("MVCPathVariableInspection")
    @RestAPI
    @RequestMapping(value = { "/api/{id}/clone_and_start",
            /* for backward compatibility */ "/api/{id}/cloneAndStart" })
    public HttpEntity<String> cloneAndStart(User user, @PathVariable("id") Long id, PerfTest perftest) {
        PerfTest test = getOneWithPermissionCheck(user, id, false);
        checkNotNull(test, "no perftest for %s exits", id);
        PerfTest newOne = test.cloneTo(new PerfTest());
        newOne.setStatus(Status.READY);
        if (perftest != null) {
            if (perftest.getScheduledTime() != null) {
                newOne.setScheduledTime(perftest.getScheduledTime());
            }
            if (perftest.getScriptRevision() != null) {
                newOne.setScriptRevision(perftest.getScriptRevision());
            }

            if (perftest.getAgentCount() != null) {
                newOne.setAgentCount(perftest.getAgentCount());
            }
        }
        if (newOne.getAgentCount() == null) {
            newOne.setAgentCount(0);
        }
        Map<String, MutableInt> agentCountMap = agentManagerService.getAvailableAgentCountMap(user);
        MutableInt agentCountObj = agentCountMap.get(isClustered() ? test.getRegion() : Config.NONE_REGION);
        checkNotNull(agentCountObj, "test region should be within current region list");
        int agentMaxCount = agentCountObj.intValue();
        checkArgument(newOne.getAgentCount() != 0, "test agent should not be %s", agentMaxCount);
        checkArgument(newOne.getAgentCount() <= agentMaxCount, "test agent should be equal to or less than %s",
                agentMaxCount);
        PerfTest savePerfTest = perfTestService.save(user, newOne);
        CoreLogger.LOGGER.info("test {} is created through web api by {}", savePerfTest.getId(), user.getUserId());
        return toJsonHttpEntity(savePerfTest);
    }
}