com.evolveum.midpoint.test.util.Lsof.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.test.util.Lsof.java

Source

/**
 * Copyright (c) 2016 Evolveum
 *
 * 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 com.evolveum.midpoint.test.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.testng.AssertJUnit;

import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;

/**
 * WARNING! Only works on Linux.
 *
 * @author semancik
 */
public class Lsof implements DebugDumpable {

    private static final Trace LOGGER = TraceManager.getTrace(Lsof.class);

    private int pid;
    private int toleranceUp = 2;
    private int toleranceDown = 10;

    private String lsofOutput;
    private int totalFds;
    private Map<String, Integer> typeMap;
    private Map<String, Integer> miscMap;
    private Map<String, String> nodeMap;

    private String baselineLsofOutput;
    private int baselineTotalFds;
    private Map<String, Integer> baselineTypeMap;
    private Map<String, Integer> baselineMiscMap;
    private Map<String, String> baselineNodeMap;

    public Lsof(int pid) {
        super();
        this.pid = pid;
    }

    public int getToleranceUp() {
        return toleranceUp;
    }

    public void setToleranceUp(int tolerance) {
        this.toleranceUp = tolerance;
    }

    public int getToleranceDown() {
        return toleranceDown;
    }

    public void setToleranceDown(int toleranceDown) {
        this.toleranceDown = toleranceDown;
    }

    public int rememberBaseline() throws NumberFormatException, IOException, InterruptedException {
        baselineTotalFds = count();
        baselineLsofOutput = lsofOutput;
        baselineTypeMap = typeMap;
        baselineMiscMap = miscMap;
        baselineNodeMap = nodeMap;

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Baseline LSOF output:\n{}", baselineLsofOutput);
        }

        return baselineTotalFds;
    }

    public int count() throws NumberFormatException, IOException, InterruptedException {
        lsofOutput = execLsof(pid);

        //      if (LOGGER.isTraceEnabled()) {
        //         LOGGER.trace("LSOF output:\n{}", lsofOutput);
        //      }

        String[] lines = lsofOutput.split("\n");

        Pattern fdPattern = Pattern.compile("(\\d+)(\\S*)");
        Pattern namePatternJar = Pattern.compile("/.+\\.jar");
        Pattern namePatternFile = Pattern.compile("/.*");
        Pattern namePatternPipe = Pattern.compile("pipe");
        Pattern namePatternEventpoll = Pattern.compile("\\[eventpoll\\]");

        typeMap = new HashMap<>();
        miscMap = new HashMap<>();
        nodeMap = new HashMap<>();

        totalFds = 0;
        for (int lineNum = 1; lineNum < lines.length; lineNum++) {
            String line = lines[lineNum];
            String[] columns = line.split("\\s+");
            String pidCol = columns[1];
            if (Integer.parseInt(pidCol) != pid) {
                throw new IllegalStateException(
                        "Unexpected pid in line " + lineNum + ", expected " + pid + "\n" + line);
            }

            String fd = columns[3];
            Matcher fdMatcher = fdPattern.matcher(fd);
            //         if (!fdMatcher.matches()) {
            //            LOGGER.trace("SKIP fd {}", fd);
            //            continue;
            //         }

            totalFds++;

            String type = columns[4];
            increment(typeMap, type);

            String node = columns[7];
            String nodeKey = node;
            if (!StringUtils.isNumeric(nodeKey)) {
                nodeKey = nodeKey + ":" + fd;
            }
            nodeMap.put(nodeKey, line);

            String name = columns[8];
            if (namePatternJar.matcher(name).matches()) {
                increment(miscMap, "jar");
            } else if (namePatternFile.matcher(name).matches()) {
                increment(miscMap, "file");
            } else if (namePatternPipe.matcher(name).matches()) {
                increment(miscMap, "pipe");
            } else if (namePatternEventpoll.matcher(name).matches()) {
                increment(miscMap, "eventpoll");
            } else if ("TCP".equals(node)) {
                increment(miscMap, "TCP");
            } else {
                increment(miscMap, "other");
            }
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("lsof counts:\n{}", debugDump(1));
        }

        return totalFds;
    }

    private void increment(Map<String, Integer> map, String key) {
        Integer typeCount = map.get(key);
        if (typeCount == null) {
            typeCount = 0;
        }
        typeCount++;
        map.put(key, typeCount);
    }

    private String execLsof(int pid) throws IOException, InterruptedException {
        Process process = null;
        String output = null;
        try {
            process = Runtime.getRuntime().exec(new String[] { "lsof", "-p", Integer.toString(pid) });
            InputStream inputStream = process.getInputStream();
            output = IOUtils.toString(inputStream, "UTF-8");
            int exitCode = process.waitFor();
            if (exitCode != 0) {
                throw new IllegalStateException("Lsof process ended with error (" + exitCode + ")");
            }
        } finally {
            if (process != null) {
                try {
                    process.getInputStream().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    process.getOutputStream().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    process.getErrorStream().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                process.destroy();
            }
        }
        return output;
    }

    public void assertStable() throws NumberFormatException, IOException, InterruptedException {
        count();
        if (!checkWithinTolerance(baselineTotalFds, totalFds)) {
            LOGGER.debug("FD situation UNSTABLE ({} -> {}):\n{}", baselineTotalFds, totalFds, debugDump(1));
            logFailDump();
            AssertJUnit.fail("Unexpected number of open FDs, expected: " + baselineTotalFds + ", but was "
                    + totalFds + " (tolerance +" + toleranceUp + "/-" + toleranceDown + ")");
        } else {
            LOGGER.debug("FD situation stable (total {})", totalFds);
        }
    }

    public void assertFdIncrease(int increase) throws NumberFormatException, IOException, InterruptedException {
        count();
        if (!checkWithinTolerance(baselineTotalFds + increase, totalFds)) {
            LOGGER.debug("Unexpected FD number increase {} ({} -> {}):\n{}", (totalFds - baselineTotalFds),
                    baselineTotalFds, totalFds, debugDump(1));
            logFailDump();
            AssertJUnit.fail("Unexpected FD number increase, expected increase " + increase + " ("
                    + (baselineTotalFds + increase) + "), but was " + (totalFds - baselineTotalFds) + " ("
                    + totalFds + ")" + " (tolerance +" + toleranceUp + "/-" + toleranceDown + ")");
        } else {
            LOGGER.debug("Expected increase of {} FDs (total {})", increase, totalFds);
        }
    }

    private boolean checkWithinTolerance(int expected, int was) {
        return (was <= (expected + toleranceUp)) && (was >= (expected - toleranceDown));
    }

    private void logFailDump() {
        LOGGER.debug("types:\n{}", diffMap(baselineTypeMap, typeMap));
        LOGGER.debug("misc:\n{}", diffMap(baselineMiscMap, miscMap));
        LOGGER.debug("nodes:\n{}", diffNodeMap());
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("LSOF output:\n{}", lsofOutput);
        }
    }

    private String diffNodeMap() {
        StringBuilder sb = new StringBuilder();
        for (Entry<String, String> baselineEntry : baselineNodeMap.entrySet()) {
            if (nodeMap.get(baselineEntry.getKey()) == null) {
                sb.append("- ").append(baselineEntry.getValue()).append("\n");
            }
        }
        for (Entry<String, String> currentEntry : nodeMap.entrySet()) {
            if (baselineNodeMap.get(currentEntry.getKey()) == null) {
                sb.append("+ ").append(currentEntry.getValue()).append("\n");
            }
        }

        return sb.toString();
    }

    private String diffMap(Map<String, Integer> baselineMap, Map<String, Integer> currentMap) {
        StringBuilder sb = new StringBuilder();
        for (Entry<String, Integer> currentEntry : currentMap.entrySet()) {
            Integer currentValue = currentEntry.getValue();
            Integer baselineValue = baselineMap.get(currentEntry.getKey());
            diff(sb, currentEntry.getKey(), baselineValue, currentValue);
        }
        for (Entry<String, Integer> baselineEntry : baselineMap.entrySet()) {
            Integer currentValue = currentMap.get(baselineEntry.getKey());
            if (currentValue == null) {
                diff(sb, baselineEntry.getKey(), baselineEntry.getValue(), currentValue);
            }
        }
        return sb.toString();
    }

    private void diff(StringBuilder sb, String key, Integer baselineValue, Integer currentValue) {
        if (baselineValue == null) {
            baselineValue = 0;
        }
        if (currentValue == null) {
            currentValue = 0;
        }
        if (baselineValue.equals(currentValue)) {
            return;
        }
        sb.append(key).append(": ");
        int diff = currentValue - baselineValue;
        if (diff > 0) {
            sb.append("+").append(diff);
        } else {
            sb.append(diff);
        }
        sb.append("\n");
    }

    @Override
    public String debugDump() {
        return debugDump(0);
    }

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.indentDebugDump(sb, indent);
        sb.append("Lsof(pid=").append(pid).append(")\n");
        DebugUtil.debugDumpWithLabelLn(sb, "baselineTotalFds", baselineTotalFds, indent + 1);
        DebugUtil.debugDumpWithLabelLn(sb, "totalFds", totalFds, indent + 1);
        DebugUtil.debugDumpWithLabelLn(sb, "typeMap", typeMap, indent + 1);
        DebugUtil.debugDumpWithLabelLn(sb, "miscMap", miscMap, indent + 1);
        // Do not display output and nodemap, that is too much
        return sb.toString();
    }

}