This second sample illustrates the use of java.time functions to report based on time and the day of the week. It reports the top 10 CPU consuming jobs by jobname between 8:30 am and 5:30 pm for each weekday.
Opening the file and reading the records work the same way as the previous sample. However in this case the processing is slightly more complicated.
Program Setup
We use a HashMap<String, JobData> to map job names to the cumulative job information. We want to report the days of the week separately, so we create a List containing a Map for each day of the week. The days of the week are numbered 1-7. We create a list of 8 entries so we can use indexes 1-7, leaving entry 0 unused.
List<HashMap<String, JobData>> jobsByDay =
new ArrayList<HashMap<String, JobData>>();
for (int i=0; i <= 7; i++)
{
jobsByDay.add(new HashMap<String, JobData>());
}
Java Time LocalTime variables are used to define the times of day we are interested in:
LocalTime primeStart = new LocalTime(8, 30);
LocalTime primeEnd = new LocalTime(17, 30);
Processing the records
Firstly we check whether the record is one we are interested in: type 30 subtype 2 and 3, written during the prime shift we defined.
if (record.recordType() == 30
&& (record.subType() == 2 || record.subType() == 3))
{
DayOfWeek recordDayOfWeek = record.smfDate().getDayOfWeek();
LocalTime recordTime = record.smfTime();
if (recordDayOfWeek.getValue() >= DayOfWeek.MONDAY.getValue()
&& recordDayOfWeek.getValue() <= DayOfWeek.FRIDAY.getValue()
&& recordTime.isAfter(primeStart)
&& recordTime.isBefore(primeEnd))
{
// process this record...
}
}
For each record that matches we need to:
- Create a Smf30Record from the SmfRecord, to give access to the type 30 information.
- Get the collection of jobs for this day of the week.
- Extract the job name from the Identification Section, and look for existing statistics for the job name.
- Update or add job statistics with information from the Processor Accounting Section.
Each type 30 record has an Identification Section, so the r30.identificationSection() returns that section directly. However, records may or may not have a Processor Accounting Section. The method r30.processorAccountingSections() returns a List which contains 0 or 1 entry. This means that we can iterate over the list and it will happily process 0 sections from records without a processor accounting section. An explicit check for the number of sections isn’t required, and the method of processing can be consistent for all section types where the number of sections is variable.
Smf30Record r30 = new Smf30Record(record);
for (ProcessorAccountingSection proc : r30.processorAccountingSections())
{
Map<String, JobData> day = jobsByDay.get(recordDayOfWeek.getValue());
String jobname = r30.identificationSection().smf30jbn();
JobData jobentry = day.get(jobname);
if (jobentry == null)
{
day.put(jobname, new JobData(proc));
}
else
{
jobentry.add(proc);
}
}
Collecting the CPU times
We collect each of CP, zIIP and zAAP time from the processor accounting section.
private static class JobData {
JobData(ProcessorAccountingSection proc)
{
add(proc);
}
public void add(ProcessorAccountingSection proc)
{
cpTime = cpTime.plus(proc.smf30cpt())
.plus(proc.smf30cps())
.minus(proc.smf30TimeOnIfa())
.minus(proc.smf30TimeOnZiip());
zaapTime = zaapTime.plus(proc.smf30TimeOnIfa());
ziipTime ziipTime.plus(proc.smf30TimeOnZiip());
}
Duration cpTime = Duration.ZERO;
Duration ziipTime = Duration.ZERO;
Duration zaapTime = Duration.ZERO;
}
Producing the Output
The writeReport() method produces the output. We produce a report for each day of the week where we collected job information. For each day we sort the jobs by CP time using the same method as Sample 1, then output the top 10 jobs.
Accumulated CPU time is written in the format hhh:mm:ss. We create a helper method to format the java.time.duration as hhmmss.
private static String hhhmmss(Duration dur)
{
long hours = dur.toHours();
long minutes = dur.minus(Duration.ofHours(hours)).toMinutes();
long seconds = dur.minus(Duration.ofHours(hours))
.minus(Duration.ofMinutes(minutes)).toMillis() / 1000;
return String.format("%d:%02d:%02d", hours, minutes, seconds);
}
EasySMF:JE provides a helper method to divide one duration by another, e.g. when calculating CPU time as a percentage of elapsed time or one job CPU time as a percentage of total CPU time.
Utils.divideDurations(jobinfo.cpTime, totalCp) * 100
Output looks like:
Tuesday
Name CPU CPU% zIIP zAAP
CICSP 1:31:32 5% 00:08 00:00
DB2PDIST 1:30:34 5% 4:55:55 00:00
DB2PDBM1 1:02:36 4% 00:00 00:00
...
The Complete Java Program
package com.blackhillsoftware.samples;
import java.io.FileInputStream;
import java.io.IOException;
import java.time.*;
import java.util.*;
import java.util.Map.*;
import com.blackhillsoftware.smf.SmfRecord;
import com.blackhillsoftware.smf.SmfRecordReader;
import com.blackhillsoftware.smf.Utils;
import com.blackhillsoftware.smf.smf30.ProcessorAccountingSection;
import com.blackhillsoftware.smf.smf30.Smf30Record;
public class PrimeShiftTopJobs
{
public static void main(String[] args) throws IOException
{
LocalTime primeStart = LocalTime.of(8, 30);
LocalTime primeEnd = LocalTime.of(17, 30);
// We need a Jobname->JobData map for each day of the week.
// So we will create a List (array) of HashMap<String, JobData>.
List<HashMap<String, JobData>> jobsByDay =
new ArrayList<HashMap<String, JobData>>();
// Populate the list for each day of the week
// We need entries for days 1-7. Entry 0 is unused
for (int i = 0; i <= 7; i++)
{
jobsByDay.add(new HashMap<String, JobData>());
}
// Read and process the data
try (SmfRecordReader reader =
args.length == 0 ?
SmfRecordReader.fromDD("INPUT") :
SmfRecordReader.fromStream(new FileInputStream(args[0])))
{
for (SmfRecord record : reader)
{
// We are only interested in type 30 subtype 2 or 3
if (record.recordType() == 30
&& (record.subType() == 2 || record.subType() == 3))
{
DayOfWeek recordDayOfWeek = record.smfDate().getDayOfWeek();
LocalTime recordTime = record.smfTime();
// check if SMF record was created during prime shift
// Monday-Friday
if (recordDayOfWeek.getValue() >= DayOfWeek.MONDAY.getValue()
&& recordDayOfWeek.getValue() <= DayOfWeek.FRIDAY.getValue()
&& recordTime.isAfter(primeStart)
&& recordTime.isBefore(primeEnd))
{
// Create a type 30 record from the original record
Smf30Record r30 = new Smf30Record(record);
// Iterate over the processor accounting sections
// Since there is either 0 or 1 section in a record, this is
// just a convenient way to skip records without the section
for (ProcessorAccountingSection proc : r30
.processorAccountingSections())
{
// get collection of jobs for this day
Map<String, JobData> day = jobsByDay
.get(recordDayOfWeek.getValue());
String jobname = r30.identificationSection().smf30jbn();
JobData jobentry = day.get(jobname);
if (jobentry == null) // nothing for this jobname,
// create new
{
day.put(jobname, new JobData(proc));
}
else
{
jobentry.add(proc);
}
}
}
}
};
}
writeReport(jobsByDay);
}
private static void writeReport(List<HashMap<String, JobData>> dailyJobs)
{
for (DayOfWeek day : DayOfWeek.values())
{
// get the jobs for the day as a List so we can sort them
List<Entry<String, JobData>> jobs = new ArrayList<Entry<String, JobData>>(
dailyJobs.get(day.getValue()).entrySet());
if (jobs.size() > 0) // if we have data for this day
{
// sort list by CPU time descending
Collections.sort(jobs, new Comparator<Entry<String, JobData>>()
{
public int compare(Entry<String, JobData> s1,
Entry<String, JobData> s2)
{
return s2.getValue().cpTime.compareTo(s1.getValue().cpTime);
}
});
// calculate total CPU for the day
Duration totalCp = Duration.ZERO;
for (Entry<String, JobData> entry : jobs)
{
totalCp = totalCp.plus(entry.getValue().cpTime);
}
// write output for top 10 job s
// Headings
System.out.format("%n%s%n", day.toString());
System.out.format("%-8s %11s %5s %11s %11s %n", "Name", "CPU",
"CPU%", "zIIP", "zAAP");
int count = 0;
for (Entry<String, JobData> entry : jobs)
{
JobData jobinfo = entry.getValue();
// write detail line
System.out.format(
"%-8s %11s %4.0f%% %11s %11s %n",
entry.getKey(), // jobname
hhhmmss(jobinfo.cpTime),
Utils.divideDurations(jobinfo.cpTime, totalCp) * 100,
hhhmmss(jobinfo.ziipTime),
hhhmmss(jobinfo.zaapTime));
// limit to top 10 jobs
if (++count >= 10)
break;
}
}
}
}
private static String hhhmmss(Duration dur)
{
long hours = dur.toHours();
long minutes = dur.minus(Duration.ofHours(hours)).toMinutes();
long seconds = dur.minus(Duration.ofHours(hours))
.minus(Duration.ofMinutes(minutes)).toMillis() / 1000;
return String.format("%d:%02d:%02d", hours, minutes, seconds);
}
private static class JobData
{
JobData(ProcessorAccountingSection proc)
{
add(proc);
}
public void add(ProcessorAccountingSection proc)
{
cpTime = cpTime.plus(proc.smf30cpt()).plus(proc.smf30cps())
.minus(proc.smf30TimeOnIfa()).minus(proc.smf30TimeOnZiip());
zaapTime = zaapTime.plus(proc.smf30TimeOnIfa());
ziipTime = ziipTime.plus(proc.smf30TimeOnZiip());
}
Duration cpTime = Duration.ZERO;
Duration ziipTime = Duration.ZERO;
Duration zaapTime = Duration.ZERO;
}
}