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.
1 Answer 1
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
.