5
\$\begingroup\$

Problem Description

A profiler is a tool which provides information about run-time performance of an application. Lets say. you are given output file format of a profiler called Jensor. Jensor captures response times of methods executed by capturing method entry and exit events. The entry and exit events are written to a log file. The log file is then processed to form a Call Trace. A Call Trace is a who called whom and when relationship. For simplicity, let us assume that the application which is profiled using Jensor is single threaded.

Your task is to create a Call Trace given a Jensor log file.

Sample

Pseudo Code

main()
{
  call A();
  call B();
  call C();
  loop(0 to 3, iterate by 1){
      call D();
  }
  sleep(20);
}

A(){
  call B();
  sleep(10);
}

B(){
  sleep(20);
}

C(){
  call A();
}

D(){
  call B();
}

JensorProfile.txt

Enter,main(),1345482572920,0,2 MB
Enter,A(),1345482572920,0,2 MB
Enter,B(),1345482572920,0,2 MB
Exit,B(),1345482572951,0,2 MB
Exit,A(),1345482572967,0,2 MB
Enter,B(),1345482572967,0,2 MB
Exit,B(),1345482572998,0,2 MB
Enter,C(),1345482572998,0,2 MB
Enter,A(),1345482572998,0,2 MB
Enter,B(),1345482572998,0,2 MB
Exit,B(),1345482573029,0,2 MB
Exit,A(),1345482573045,0,2 MB
Exit,C(),1345482573045,0,2 MB
Enter,D(),1345482573045,0,2 MB
Enter,B(),1345482573045,0,2 MB
Exit,B(),1345482573076,0,2 MB
Exit,D(),1345482573076,0,2 MB
Enter,D(),1345482573076,0,2 MB
Enter,B(),1345482573076,0,2 MB
Exit,B(),1345482573096,0,2 MB
Exit,D(),1345482573096,0,2 MB
Enter,D(),1345482573096,0,2 MB
Enter,B(),1345482573096,0,2 MB
Exit,B(),1345482573116,0,2 MB
Exit,D(),1345482573116,0,2 MB
Enter,D(),1345482573116,0,2 MB
Enter,B(),1345482573116,0,2 MB
Exit,B(),1345482573136,0,2 MB
Exit,D(),1345482573136,0,2 MB     
Exit,main(),1345482573156,1,2 MB

where,

Column 1 is name of the event. Possible events {Enter, Exit}

Column 2 is name of the method which is entered into or exited out of

Column 3 is time in milliseconds since epoch (1st Jan 1970)

Column 4 is the response time in milliseconds

Column 5 is memory consumption during that event

Columns are delimited by “,” (comma character). Row is delimited by a newline character. Each row contains a single event.

Jensor log file JensorProfile.txt is a representation of the sample program (pseudocode).

Your task is to read similar JensorProfile.txt files as input and find the nth invocation of the function in that JensorProfile.txt file.

The output should include the name of the function corresponding to that invocation, the response time of that function and total number of invocations of that function in the entire run of that program.

enter image description here


Code

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;

/**
 *
 * @author tintinmj
 */
public class JensorLogProcessor {

    public static class JensorLogProcessorException extends Exception {

        public JensorLogProcessorException(String s) {
            super(s);
        }

    }

    public static class InvalidInputException extends JensorLogProcessorException {

        public InvalidInputException() {
            super("Invalid Input");
        }

    }

    private class JensorObject {
        private final String functionName;
        private final long startTime;
        private long executionTime;

        public JensorObject(String functionName, long startTime, long executionTime) {
            this.functionName = functionName;
            this.startTime = startTime;
            this.executionTime = executionTime;
        }

        public void calculateExecutionTime(long endTime) {
            this.executionTime = endTime - startTime;
        }

        public long getExecutionTime() {
            return executionTime;
        }

        public String getFunctionName() {
            return functionName;
        }
    }

    @SuppressWarnings("FieldMayBeFinal")
    private static Deque<JensorObject> stack = new ArrayDeque<>();
    private static List<JensorObject> listOfProcesses = new ArrayList<>();
    private static Map<String, Integer> numberOfFunctionCalled = new HashMap<>();

    public JensorLogProcessor() {

    }

    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            String logFileName = sc.nextLine();
            int functionNumber = sc.nextInt();

            System.out.println(new JensorLogProcessor().getDetailsOfFunctionFromFile(logFileName, functionNumber));
        } catch (NoSuchElementException | InvalidInputException e){
            System.out.println("Invalid Input");
        }
    }

    public String getDetailsOfFunctionFromFile(String fileName, int functionNumber) throws InvalidInputException {
        parseLogFile(fileName);
            if (functionNumber > listOfProcesses.size()) {
                throw new InvalidInputException();
            }
            else {
                JensorObject jensorObject = listOfProcesses.get(functionNumber - 1); // 0 based indexing
                String functionName = jensorObject.getFunctionName();
                long responseTime = jensorObject.getExecutionTime();
                int numberOfInvocation = numberOfFunctionCalled.get(functionName);

                return String.format("%s\n%s\n%s", functionName,responseTime,numberOfInvocation);
            }
    }

    private void parseLogFile(String fileName) throws InvalidInputException {
        try {
            BufferedReader reader = new BufferedReader(new FileReader(fileName));
            String line = reader.readLine();
            while (line != null) {
                parseFunctionAndProcess(line);
                line = reader.readLine();
            }
        } catch (IOException e) {
            throw new InvalidInputException();
        }
    }

    private void parseFunctionAndProcess(String line) throws InvalidInputException {
        try {
            String[] functionDesc = line.split(",");
            String enterOrExit = functionDesc[0];
            String functionName = functionDesc[1];
            long ticks = Long.parseLong(functionDesc[2]);

            if(null != functionDesc[0]) switch (enterOrExit) {
                case "Enter":
                    JensorObject jensorObject = new JensorObject(functionName,
                            ticks,  // start ticks
                            0L      // at starting execution ticks is 0
                    );
                    stack.push(jensorObject);
                    listOfProcesses.add(jensorObject);
                    if(numberOfFunctionCalled.containsKey(jensorObject.getFunctionName())) {
                        numberOfFunctionCalled.put(jensorObject.getFunctionName(),
                                numberOfFunctionCalled
                                        .get(jensorObject.getFunctionName())+1);
                    }
                    else {
                        numberOfFunctionCalled.put(jensorObject.getFunctionName(), 1);
                    }   break;
                case "Exit":
                    JensorObject currentJensorObject = stack.pop();
                    currentJensorObject.calculateExecutionTime(ticks);
                    break;
            }

        } catch (NumberFormatException e) {
            throw new InvalidInputException();
        }
    }
}

My code got accepted. But I want suggestion from all of you to improvement in any aspects.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Unit testing

It's good to convert that main method to make your implementation testable:

public static String perform(Scanner sc) {
    try {
        String logFileName = sc.nextLine();
        int functionNumber = sc.nextInt();

        return new JensorLogProcessor().getDetailsOfFunctionFromFile(logFileName, functionNumber);
    } catch (NoSuchElementException | InvalidInputException e){
        return "Invalid Input";
    }
}

public static void main(String[] args) {
    System.out.println(perform(new Scanner(System.in)));
}

Now you can add unit tests:

public class JensorLogProcessorTest {
    @Test
    public void testSample1() {
        assertEquals("D()\n20\n4", JensorLogProcessor.process(new Scanner("/tmp/JensorProfile.txt\n10")));
    }

    @Test
    public void testSample2() {
        assertEquals("C()\n47\n2", JensorLogProcessor.process(new Scanner("/tmp/JensorProfile.txt\n5")));
    }

    @Test
    public void testInvalidInput() throws IOException {
        assertEquals("Invalid Input", JensorLogProcessor.process(new Scanner("/tmp/JensorProfile.txt\na")));
    }
}

Input validation

On one hand it's "nice" that you validate input. But if it's not part of the specification, I think it's safe to assume that the program will receive correct inputs.

Does the contest environment expect the text "Invalid Input"? If yes, then it you could move this string to a constant, especially because you repeated it twice in the code.

If the problem description doesn't specify error handling, then I think you can omit it, and let all the exceptions just bubble up all the way to main.

Similarly, in here:

if(null != functionDesc[0]) switch (enterOrExit) {

The null check is unnecessary. And if it was, then since you already assigned functionDesc[0] to enterOrExit, it would have been better this way:

if(null != enterOrExit) switch (enterOrExit) {

Naming

numberOfFunctionCalled is not a great name for a mapping of function names to invocation counts. Perhaps functionInvocationCounts would be better.

JensorObject is not a great name for function invocation details. Perhaps FunctionInvocationInfo would be better.

Minor things

Instead of this:

String line = reader.readLine();
while (line != null) {
    parseFunctionAndProcess(line);
    line = reader.readLine();
}

It's better to eliminate the duplicated .readLine call:

String line;
while ((line = reader.readLine()) != null) {
    parseFunctionAndProcess(line);
}

The empty constructor of JensorLogProcessor is pointless, you can delete it.


I don't really understand why you suppress this warning:

@SuppressWarnings("FieldMayBeFinal")
private static Deque<JensorObject> stack = new ArrayDeque<>();
private static List<JensorObject> listOfProcesses = new ArrayList<>();
private static Map<String, Integer> numberOfFunctionCalled = new HashMap<>();

Why not just make those static fields final?


You forgot to close the BufferedReader in parseLogFile.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.