Commit a34a480c authored by malenov's avatar malenov
Browse files

Python script to plot detailed memory analysis for intra-frame and inter-frame...

Python script to plot detailed memory analysis for intra-frame and inter-frame memory blocks (based on pandas package)
parent ddbf7020
Loading
Loading
Loading
Loading
+114 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3

import os
import argparse
import csv
import sys
import struct
import numpy as np
import pandas as pd

import matplotlib
import tkinter
matplotlib.use('TkAgg')   # Qt4Agg  TkAgg Gtk3Agg
import matplotlib.pyplot as plt

# set matplotlib in interactive mode (plots figure and return control)
plt.ion()
plt.close('all')

parser = argparse.ArgumentParser(description='Analyze memory output file')
parser.add_argument('input',type=str,help='Memory analysis file (e.g. mem_analysis.csv)')
args = parser.parse_args()
fileIn  = args.input

# read input .csv file
df = pd.read_csv(fileIn, header=None, names=['action_type', 'frame', 'name', 'line_no', 'mem_size'])   
Nframes = max(df['frame'])

# merge identical memory blocks -> create new column 'count'
df = df.groupby(df.columns.tolist(),as_index=False).size()
df = df.rename(columns={'size': 'count'})

# df.loc[df['action_type'] == 'D', 'frame'] += 0.9

# remove column 'action_type'
df.drop(columns = ['action_type'])

# merge records with the same signature but different frame number -> create list of frames (sort in ascending order)
df = df.groupby(['name', 'line_no', 'mem_size', 'count'],as_index=False).agg({'frame': list})
df['frame'] = df['frame'].apply(np.sort)
df['list_len'] = df['frame'].apply(len)      # auxiliary column -> length of the list

# merge records with the same name and frame but different line number -> sum memory sizes and counts
df = df.groupby(['name', 'list_len'],as_index=False).agg({'line_no': list, 'mem_size': sum, 'count':sum, 'frame':'first'})

# sort by memory size (put inter-frame memory blocks to the bottom)
select_inter = (df['list_len'] == 2) & (df.apply(lambda row : row['frame'][-1] - row['frame'][0], axis = 1) >= Nframes)
df_inter = df[select_inter]
df_inter = df_inter.sort_values(by=['mem_size'], ascending=False)
df_intra = df[~select_inter]
df_intra = df_intra.sort_values(by=['list_len'], ascending=True)

# plot inter-frame memory blocks in horizontal bar graph (use .broken_barh method)
fig, ax = plt.subplots()
colors = iter(plt.cm.rainbow(np.linspace(0, 1, len(df.index))))
y_ticks = []
y_tick_labels = []
y_low = 1
for index, row in df_inter.iterrows():
    l = f"{row['name']} on lines: {row['line_no']}, total memory size: {row['count']}x{row['mem_size']}"
    fms = row['frame'].reshape((-1,2))  # convert frame numbers to two-column format
    fms[:,1] -= fms[:,0]                # second column should now represent the duration of the segment
    fmsT = fms.T
    fms_list = list(zip(fmsT[0], fmsT[1]))
    c = next(colors)
    ax.broken_barh(xranges=fms_list, yrange=(y_low, row['count']), label=l, color=c)
    y_ticks.append(y_low + 0.5 * row['count'])
    y_tick_labels.append(row['name'])
    y_low += row['count'] + 0.5
    print(l + f", {row['count'] * fms.shape[0]/Nframes:.3f} mallocs per frame")
   
# plot intra-frame memory blocks in horizontal bar graph (use .broken_barh method)
df_intra = df_intra.groupby(['name'],as_index=False).agg({'list_len': list, 'line_no': list, 'mem_size': list, 'count': list, 'frame': list}) 
for index, row in df_intra.iterrows():
    # repeat for all grouped memory records in the row
    for j in range(len(row['list_len'])):
        l = f"{row['name']} on lines: {row['line_no'][j]}, total memory size: {row['count'][j]}x{row['mem_size'][j]}"
        fms = row['frame'][j].astype(float).reshape((-1,2))  # convert frame numbers to two-column format
        fms[:,1] -= fms[:,0]                # second column should now represent the duration of the segment
        idx_zero = fms[:,1] == 0            # find zero-length segments -> these are intra-frame memory blocks
        fms[idx_zero, 0] += 0.1
        fms[idx_zero, 1] = 0.8
        fmsT = fms.T
        fms_list = list(zip(fmsT[0], fmsT[1]))
        c = next(colors)
        ax.broken_barh(xranges=fms_list, yrange=(y_low, row['count'][j]), label=l, color=c, alpha=0.6)
        print(l + f", {row['count'][j] * fms.shape[0]/Nframes:.3f} mallocs per frame")
    y_ticks.append(y_low + 0.5 * max(row['count']))
    y_tick_labels.append(row['name'])
    y_low += max(row['count']) + 0.5    

ax.set_yticks(y_ticks)
ax.set_yticklabels(y_tick_labels)
ax.set_ylabel('Memory size')
ax.set_xlabel('Frame')
ax.set_title('Memory analysis')

# shrink current axis's height by 20% on the bottom to fit the legend
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * 0.2, box.width, box.height * 0.8])
         
# insert the legend below the graph         
ax.legend(loc="upper left", bbox_to_anchor=(0, -0.1), ncol=2, borderaxespad=0, fontsize='small')

# magnify to "almost" fullscreen size
mng = plt.get_current_fig_manager()
mng.full_screen_toggle()
# mng.frame.Maximize(True)
# mng.window.showMaximized()
# mng.window.state('zoomed')

# show and save the figure
plt.show()
# plt.savefig(os.path.splitext(args.input)[0] + '.png', dpi=600)