Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1381 lines (1131 sloc) 35 KB
/*
This file is part of memview, a real-time memory trace visualization
application.
Copyright (C) 2013 Andrew Clinton
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file COPYING.
*/
#include "Window.h"
#include "Color.h"
#include "MemoryState.h"
#include "Loader.h"
#include <fstream>
#include <sys/ptrace.h>
#include <sys/wait.h>
#define USE_PBUFFER
static const QSize theDefaultSize(800, 600);
LogSlider::LogSlider(const char *name, int maxlogval, int deflogval)
{
mySlider = new QSlider(Qt::Horizontal);
mySlider->setRange(0, maxlogval);
mySlider->setSingleStep(1);
mySlider->setPageStep(5);
mySlider->setTickPosition(QSlider::TicksBothSides);
mySlider->setTracking(true);
mySlider->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored);
mySlider->setFixedWidth(300);
myLabel = new QLabel(name);
myNumber = new QLabel;
myNumber->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored);
myNumber->setFixedWidth(50);
myNumber->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(myLabel);
layout->addWidget(mySlider);
layout->addWidget(myNumber);
setLayout(layout);
connect(mySlider, SIGNAL(valueChanged(int)), this, SLOT(fromLog(int)));
mySlider->setValue(deflogval);
fromLog(mySlider->value());
}
void
LogSlider::setLogValue(int value)
{
mySlider->setValue(value);
}
void LogSlider::fromLog(int value)
{
value = 1 << value;
myNumber->setNum(value);
emit valueChanged(value);
}
Window::Window(int argc, char *argv[])
{
const char *batchsize = extractOption(argc, argv, "--batch-size=");
myScrollArea = new MemViewScroll(this);
setStatusBar(statusBar());
// The constructor for MemViewWidget will handle falling back to OpenGL 3.0
// if it finds that 3.3 isn't supported
QGLFormat format(QGL::NoDepthBuffer);
QGLFormat::OpenGLVersionFlags flags = QGLFormat::openGLVersionFlags();
if (!(flags & QGLFormat::OpenGL_Version_3_3))
{
fprintf(stderr, "Detected old OpenGL version, trying to force it to 3.3\n");
// Try to force the version to 3.3. This seems to correct problems with
// mesa, where GL 3.3 is only supported in core profile.
format.setVersion(3,3);
}
//format.setProfile(QGLFormat::CompatibilityProfile);
//format.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0
//format.setSampleBuffers(true);
myMemView = new MemViewWidget(format, argc, argv,
myScrollArea,
myScrollArea->verticalScrollBar(),
myScrollArea->horizontalScrollBar(),
statusBar());
myQuit = new QAction(tr("&Quit"), this);
connect(myQuit, SIGNAL(triggered()), qApp, SLOT(quit()));
myFileMenu = menuBar()->addMenu(tr("&File"));
myFileMenu->addSeparator();
myFileMenu->addAction(myQuit);
myLayoutMenu = menuBar()->addMenu(tr("&Layout"));
static const char *theVisNames[theVisCount] = {
"&Hilbert Curve",
"&Recursive Block",
"&Linear",
};
myVisGroup = createActionGroup(
myLayoutMenu, theVisNames, myVis, theVisCount, 0);
myLayoutMenu->addSeparator();
static const char *theLayoutNames[theLayoutCount] = {
"&Compact",
"&Full Size",
};
myLayoutGroup = createActionGroup(
myLayoutMenu, theLayoutNames, myLayout, theLayoutCount, 0);
connect(myVis[0], SIGNAL(triggered()), myMemView, SLOT(hilbert()));
connect(myVis[1], SIGNAL(triggered()), myMemView, SLOT(block()));
connect(myVis[2], SIGNAL(triggered()), myMemView, SLOT(linear()));
connect(myLayout[0], SIGNAL(triggered()), myMemView, SLOT(compact()));
connect(myLayout[1], SIGNAL(triggered()), myMemView, SLOT(full()));
static const char *theDisplayNames[theDisplayCount] = {
"&Read/Write",
"&Thread Id",
"&Data Type",
"&Mapped Regions",
"&Stack Traces",
};
myDisplayMenu = menuBar()->addMenu(tr("&Display"));
myDisplayGroup = createActionGroup(
myDisplayMenu, theDisplayNames, myDisplay, theDisplayCount, 0);
myDisplayMenu->addSeparator();
myDisplayDimmer = new QAction(tr("&Limit Brightness"), this);
myDisplayDimmer->setCheckable(true);
myDisplayMenu->addAction(myDisplayDimmer);
myDisplayShowToolBar = new QAction(tr("&Show Toolbar"), this);
myDisplayShowToolBar->setCheckable(true);
myDisplayMenu->addAction(myDisplayShowToolBar);
connect(myDisplayGroup, SIGNAL(triggered(QAction *)),
myMemView, SLOT(display(QAction *)));
connect(myDisplayDimmer, SIGNAL(triggered()),
myMemView, SLOT(dimmer()));
connect(myDisplayShowToolBar, SIGNAL(toggled(bool)),
this, SLOT(toolbar(bool)));
// This menu should be ordered the same as the MV_Data* defines in
// mv_ipc.h
static const char *theDataTypeNames[theDataTypeCount] = {
"&Auto-Detect Types",
"&32-bit Integer",
"&64-bit Integer",
"&32-bit Float",
"&64-bit Float",
"&Ascii String",
};
myDataTypeMenu = menuBar()->addMenu(tr("&Data Type"));
myDataTypeGroup = createActionGroup(
myDataTypeMenu, theDataTypeNames, myDataType, theDataTypeCount, 0);
connect(myDataTypeGroup, SIGNAL(triggered(QAction *)),
myMemView, SLOT(datatype(QAction *)));
setWindowTitle("Memview");
myScrollArea->setViewport(myMemView);
setCentralWidget(myScrollArea);
myToolBar = new QToolBar("Tools");
myToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
LogSlider *slider = new LogSlider("Batch Size", 15, 15);
myToolBar->addWidget(slider);
connect(slider, SIGNAL(valueChanged(int)), myMemView, SLOT(batchSize(int)));
if (batchsize)
{
int value = (int)(log((double)atoi(batchsize))/log(2.0));
slider->setLogValue(value);
}
}
Window::~Window()
{
}
QSize
Window::sizeHint() const
{
return theDefaultSize;
}
void
Window::toolbar(bool value)
{
if (value)
{
addToolBar(Qt::TopToolBarArea, myToolBar);
myToolBar->show();
}
else
removeToolBar(myToolBar);
}
QActionGroup *
Window::createActionGroup(
QMenu *menu,
const char *names[],
QAction *actions[],
int count,
int def_action)
{
QActionGroup *group = new QActionGroup(this);
for (int i = 0; i < count; i++)
{
actions[i] = new QAction(tr(names[i]), group);
actions[i]->setCheckable(true);
menu->addAction(actions[i]);
}
actions[def_action]->setChecked(true);
return group;
}
//
// MemViewWidget
//
MemViewWidget::MemViewWidget(QGLFormat fmt,
int argc, char *argv[],
QWidget *parent,
QScrollBar *vscrollbar,
QScrollBar *hscrollbar,
QStatusBar *status)
: QGLWidget(fmt, parent)
, myVScrollBar(vscrollbar)
, myHScrollBar(hscrollbar)
, myStatusBar(status)
, myProgram(0)
, myTexture(0)
, myColorTexture(0)
, myPixelBuffer(0)
, myPrevEvents(0)
, myZoom(0)
, myDisplayMode(0)
, myDisplayDimmer(0)
, myDataType(-1)
, myStopWatch(false)
, myPaintInterval(false)
, myEventTimer(false)
, myDragging(false)
{
// Extract the path to the executable
myPath = argv[0];
size_t pos = myPath.rfind('/');
if (pos != std::string::npos)
myPath.resize(pos+1);
else
myPath = "";
// Skip the program name
argc -= 1;
argv += 1;
// We need mouse events even when no buttons are held down, for status
// bar updates
setMouseTracking(true);
// Use a fixed-width font for the status bar
QFont font("Monospace");
font.setStyleHint(QFont::TypeWriter);
myStatusBar->setFont(font);
myStatusMessage = new QLabel;
myStatusMessage->setAlignment(Qt::AlignLeft);
myStatusBar->addWidget(myStatusMessage, 1);
myStatusZoom = new QLabel;
myStatusZoom->setAlignment(Qt::AlignRight);
myStatusBar->addWidget(myStatusZoom, 1);
const char *ignore = extractOption(argc, argv, "--ignore-bits=");
int ignorebits = ignore ? atoi(ignore) : 2;
myState = new MemoryState(ignorebits);
myZoomState = myState;
myStackTrace = new StackTraceMap;
myStackSelection = 0;
myMMapMap = new MMapMap;
myLoader = new Loader(myState, myStackTrace, myMMapMap, myPath);
if (myLoader->openPipe(argc, argv))
{
// Start loading data in a new thread
myLoader->start();
}
myFastTimer = startTimer(30);
mySlowTimer = startTimer(500);
myPaintInterval.start();
myEventTimer.start();
makeCurrent();
QGLFormat::OpenGLVersionFlags flags = QGLFormat::openGLVersionFlags();
if (!(flags & QGLFormat::OpenGL_Version_3_3))
{
// Trying 3.0
fmt.setVersion(3,0);
setFormat(fmt);
}
}
MemViewWidget::~MemViewWidget()
{
delete myLoader;
delete myState;
delete myStackTrace;
delete myMMapMap;
}
void
MemViewWidget::linear()
{
myDisplay.setVisualization(DisplayLayout::LINEAR);
update();
}
void
MemViewWidget::block()
{
myDisplay.setVisualization(DisplayLayout::BLOCK);
changeZoom(myZoom);
update();
}
void
MemViewWidget::hilbert()
{
myDisplay.setVisualization(DisplayLayout::HILBERT);
changeZoom(myZoom);
update();
}
void
MemViewWidget::compact()
{
myDisplay.setCompact(true);
changeZoom(myZoom);
update();
}
void
MemViewWidget::full()
{
myDisplay.setCompact(false);
changeZoom(myZoom);
update();
}
void MemViewWidget::display(QAction *action)
{
myDisplayMode = action->actionGroup()->actions().indexOf(action);
}
void MemViewWidget::dimmer() { myDisplayDimmer = !myDisplayDimmer; }
void MemViewWidget::datatype(QAction *action)
{
// Subtract 1 so that auto-detect types is -1
myDataType = action->actionGroup()->actions().indexOf(action) - 1;
}
void MemViewWidget::batchSize(int value)
{
myLoader->setBlockSize(value);
}
// Load a file into a buffer. The buffer is owned by the caller, and
// should be freed with delete[].
static char *
loadTextFile(const char *filename, const std::vector<std::string> &paths)
{
std::ifstream is;
for (auto it = paths.begin(); it != paths.end(); ++it)
{
std::string path = *it + filename;
is.open(path.c_str());
if (is.good())
break;
}
if (!is.good())
return 0;
is.seekg(0, std::ios::end);
long length = is.tellg();
is.seekg(0, std::ios::beg);
if (!length)
return 0;
char *buffer = new char[length+1];
is.read(buffer, length);
buffer[length] = '\0';
return buffer;
}
bool
loadShaderProgram(QGLShader *shader,
const char *filename,
const char *version,
const std::vector<std::string> &paths,
const unsigned char *def, int deflen)
{
char *src = loadTextFile(filename, paths);
if (!src)
{
src = new char[deflen+1];
memcpy(src, def, deflen);
src[deflen] = '\0';
}
// Insert the version string into the shader source
std::string src_with_version = version;
src_with_version += src;
delete [] src;
return shader->compileSourceCode(src_with_version.c_str());
}
// 512 size texture to accomodate the 500 possible threads supported by
// valgrind.
static const int theColorBits = 9;
static const int theColorSize = (1 << theColorBits);
static int
rinverse(int val)
{
// Radical inverse specialized for 16 bits
int tmp = val;
tmp = ((tmp & 0xAAAA) >> 1) | ((tmp & 0x5555) << 1);
tmp = ((tmp & 0xCCCC) >> 2) | ((tmp & 0x3333) << 2);
tmp = ((tmp & 0xF0F0) >> 4) | ((tmp & 0x0F0F) << 4);
tmp = ((tmp & 0xFF00) >> 8) | ((tmp & 0x00FF) << 8);
return tmp >> (16-theColorBits);
}
static void
fillThreadColors(GLImage<uint32> &colors)
{
const int width = theColorSize;
const float s = 0.75;
const float v = 1;
Color clr;
colors.resize(width, 1);
for (int i = 0; i < width; i++)
{
int idx = rinverse(i);
float h = idx / (float)(width-1);
clr.fromHSV(h, s, v);
colors.setPixel(i, 0, clr.toInt32());
}
}
void
MemViewWidget::initializeGL()
{
// We're doing our own dithering. Though having this enabled didn't
// seem to produce any dithering.
glDisable(GL_DITHER);
const GLuint type = GL_NEAREST;
// Build a texture to store colors used by the shader
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &myColorTexture);
glBindTexture(GL_TEXTURE_1D, myColorTexture);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, type);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, type);
GLImage<uint32> colors;
fillThreadColors(colors);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA,
colors.width(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, colors.data());
// Create the memory state texture
glGenBuffers(1, &myPixelBuffer);
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &myTexture);
glBindTexture(GL_TEXTURE_RECTANGLE, myTexture);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, type);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, type);
std::vector<std::string> paths;
paths.push_back("");
paths.push_back(myPath);
paths.push_back("/usr/share/memview/");
QGLShader *vshader = new QGLShader(QGLShader::Vertex, this);
QGLShader *fshader = new QGLShader(QGLShader::Fragment, this);
const char *version;
const QGLFormat &fmt = format();
std::pair<int,int> version_pair(fmt.majorVersion(), fmt.minorVersion());
if (version_pair >= std::pair<int,int>(3,3))
{
// OpenGL 3.3 or newer, use it
version = "#version 330\n\n";
}
else
{
// Try compiling shader for OpenGL 3.0 (GLSL 1.30) with
// GL_EXT_gpu_shader4 for integer textures
version = "#version 130\n"
"#extension GL_EXT_gpu_shader4 : enable\n\n";
}
bool success = true;
success &= loadShaderProgram(vshader, "memview.vert", version, paths, 0, 0);
success &= loadShaderProgram(fshader, "memview.frag", version, paths, 0, 0);
if (success)
{
myProgram = new QGLShaderProgram(this);
myProgram->addShader(vshader);
myProgram->addShader(fshader);
myProgram->bindAttributeLocation("pos", 0);
if (!myProgram->link())
{
delete myProgram;
myProgram = 0;
}
}
else
{
delete vshader;
delete fshader;
}
}
void
MemViewWidget::resizeGL(int width, int height)
{
glViewport(0, 0, (GLint)width, (GLint)height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
resizeImage(myZoom);
}
void
MemViewWidget::resizeImage(int zoom)
{
int w = width();
int h = height();
if (zoom < 0)
{
w >>= (-zoom) >> 1; w = SYSmax(w, 1);
h >>= (-zoom) >> 1; h = SYSmax(h, 1);
}
myVScrollBar->setPageStep(h);
myHScrollBar->setPageStep(w);
#ifdef USE_PBUFFER
myImage.setSize(w, h);
#else
myImage.resize(w, h);
#endif
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, myPixelBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, myImage.bytes(), 0, GL_STREAM_DRAW);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
static void
setScrollMax(QScrollBar *scroll, int64 size, bool with_margin = true)
{
int64 margin = 0;
// Allow up to half the state to be hidden outside the window
if (with_margin)
margin = scroll->pageStep() -
(SYSmin(size, (int64)scroll->pageStep()) >> 1);
int64 nmax = SYSmax(size - scroll->pageStep() + margin, 0ll);
scroll->setMaximum(SYSclamp32(nmax));
scroll->setMinimum(SYSclamp32(-margin));
}
static void
clampResToInteger(int &off, int &res, int64 off64, uint64 res64)
{
uint64 maxres = 1ull << 30;
if (res64 > maxres)
{
if (off64 > int64(res64>>1))
off64 -= int64(res64 - maxres);
res64 = maxres;
}
off = SYSclamp32(off64);
res = SYSclamp32(res64);
}
void
MemViewWidget::paintGL()
{
#if 0
StopWatch timer;
fprintf(stderr, "interval %f time ", myPaintInterval.lap());
#endif
myDisplay.update(
*myState, *myMMapMap, width(), myImage.width(), myZoom);
int64 roff = myHScrollBar->value();
int64 coff = myVScrollBar->value();
#ifdef USE_PBUFFER
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, myPixelBuffer);
myImage.setData((uint32 *)
glMapBufferARB(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
#endif
switch (myDisplayMode)
{
case 3:
myDisplay.fillImage(myImage, IntervalSource<MMapInfo>(
*myMMapMap, 0, myZoomState->getIgnoreBits()),
roff, coff);
break;
case 4:
myDisplay.fillImage(myImage, IntervalSource<StackInfo>(
*myStackTrace, myStackSelection,
myZoomState->getIgnoreBits()),
roff, coff);
break;
default:
if (myZoom <= 0 || !myZoomState->isSamplingInProgress())
{
myDisplay.fillImage(myImage, StateSource(*myZoomState),
roff, coff);
}
else
{
myDisplay.fillImage(myImage, SampledStateSource(*myState, myZoom),
roff, coff);
}
break;
}
#ifdef USE_PBUFFER
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
#endif
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE, myTexture);
#ifdef USE_PBUFFER
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32UI,
myImage.width(), myImage.height(), 0, GL_RED_INTEGER,
GL_UNSIGNED_INT, 0 /* offset in PBO */);
#else
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32UI,
myImage.width(), myImage.height(), 0, GL_RED_INTEGER,
GL_UNSIGNED_INT, myImage.data());
#endif
// Unbind the buffer - this is required for text rendering to work
// correctly.
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
if (myProgram)
{
myProgram->bind();
myProgram->setUniformValue("theState", 0);
myProgram->setUniformValue("theColors", 1);
myProgram->setUniformValue("theStale", MemoryState::theStale);
myProgram->setUniformValue("theHalfLife", MemoryState::theHalfLife);
myProgram->setUniformValue("theDisplayMode", myDisplayMode);
myProgram->setUniformValue("theDisplayDimmer", myDisplayDimmer);
myProgram->setUniformValue("theTime", myState->getTime());
myProgram->setUniformValue("theWindowResX", width());
myProgram->setUniformValue("theWindowResY", height());
// Since the shader program only uses single precision integers, adjust
// the offset and display size so that they are still relatively
// correct but fit within the integer precision limit.
int offx, offy;
int resx, resy;
clampResToInteger(offx, resx, myHScrollBar->value(), myDisplay.width());
clampResToInteger(offy, resy, myVScrollBar->value(), myDisplay.height());
myProgram->setUniformValue("theDisplayOffX", offx);
myProgram->setUniformValue("theDisplayOffY", offy);
myProgram->setUniformValue("theDisplayResX", resx);
myProgram->setUniformValue("theDisplayResY", resy);
}
else
{
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
setScrollMax(myVScrollBar, myDisplay.height());
setScrollMax(myHScrollBar, myDisplay.width(),
myDisplay.getVisualization() != DisplayLayout::LINEAR);
// Create vertex buffers for a full-screen quad (as a triangle strip of 2
// triangles)
const GLfloat pos[4][2] = {
{-1, -1},
{ 1, -1},
{-1, 1 },
{ 1, 1 }
};
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), pos, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Delete the vertex array for each render call. This seems to be required
// to mix vertex arrays with calls to renderText() (in paintText() below).
glDisableVertexAttribArray(0);
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
if (myProgram)
{
myProgram->release();
}
// Render memory contents as text if we're at a sufficient zoom level
paintText();
update();
}
static inline bool
isFloatType(int datatype)
{
switch (datatype)
{
case MV_DataFlt32:
case MV_DataFlt64:
return true;
}
return false;
}
static inline int
getAlignBits(int datatype)
{
switch (datatype)
{
case MV_DataInt64:
case MV_DataFlt64:
return 3;
}
return 2;
}
template <typename FT, typename IT>
static inline FT
intToFloat(IT val)
{
union {
FT fval;
IT ival;
} uval;
uval.ival = val;
return uval.fval;
}
// Peek at a 64-bit value in the tracee
static bool
peekData(pid_t pid, uint64 qaddr, uint64 &buf64)
{
long peekval = ptrace(PTRACE_PEEKDATA,
pid, (void *)qaddr, NULL);
if (peekval == -1)
return false;
buf64 = (uint64)peekval;
if (sizeof(long) == 4)
{
// long is a different size on 32-bit platforms...
peekval = ptrace(PTRACE_PEEKDATA,
pid, (void *)(qaddr + 4), NULL);
if (peekval == -1)
return false;
buf64 |= (uint64)peekval << 32;
}
return true;
}
struct Text {
int x;
int y;
QString str;
};
void
MemViewWidget::paintText()
{
const int xmargin = 4;
int pwidth = width() / myImage.width();
int pheight = height() / myImage.height();
QFont font;
QFontMetrics metrics(font);
if (pheight < 2*metrics.height())
return;
// Use ptrace to stop the process and inspect data
pid_t pid = myLoader->getChild();
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL))
return;
// Wait for the process to stop on a signal
waitpid(pid, 0, 0);
std::vector<Text> text_list;
for (int i = 0; i < myImage.height(); i++)
{
for (int j = 0; j < myImage.width(); j++)
{
uint64 qaddr = myDisplay.queryPixelAddress(*myState,
myHScrollBar->value() + j,
myVScrollBar->value() + i);
if (!qaddr)
continue;
// Either use the specified data type or if it's -1, get the
// type from the value
int datatype = myDataType;
if (datatype < 0)
{
uint64 off;
auto page = myState->getPage(qaddr, off);
if (page.exists())
datatype = page.state(off).dtype();
else
datatype = MV_DataInt32;
}
const uint64 min_align_bits = getAlignBits(datatype);
const uint64 min_align = 1 << min_align_bits;
qaddr <<= myState->getIgnoreBits();
if (qaddr & (min_align-1))
continue;
// Get the value. If the address wasn't mapped, peekData()
// will return false.
uint64 val;
if (!peekData(pid, qaddr, val))
continue;
int x = (j*width())/myImage.width() + xmargin;
int y = (i*height())/myImage.height() +
(pheight + metrics.height())/2;
QString str;
if (min_align_bits == 2)
{
val &= 0xFFFFFFFF;
if (datatype == MV_DataChar8)
{
char cstr[4];
bool valid = true;
for (int i = 0; i < 4; i++)
{
cstr[i] = (char)((val >> (i*8)) & 0xFF);
valid &= isascii(cstr[i]);
}
if (valid)
str.sprintf("\"%c%c%c%c\"",
cstr[0],
cstr[1],
cstr[2],
cstr[3]);
else
str.sprintf("%x", (uint32)val);
}
else
{
if (isFloatType(datatype))
str.sprintf("%f", intToFloat<float>((uint32)val));
else
str.sprintf("%x", (uint32)val);
}
}
else
{
if (isFloatType(datatype))
str.sprintf("%g", intToFloat<double>(val));
else
str.sprintf("%llx", val);
}
// Shorten the text so that it fits within the desired width.
// This will add the "..." if it's too long.
str = metrics.elidedText(
str, Qt::ElideRight, pwidth - 2*xmargin);
text_list.push_back(Text{x, y, str});
}
}
// Detach - this will restart the process
ptrace(PTRACE_DETACH, pid, NULL, NULL);
// Render text after restarting the process so that we're only slowing
// down draw time (not execution)
for (auto it = text_list.begin(); it != text_list.end(); ++it)
renderText(it->x, it->y, it->str, font);
}
bool
MemViewWidget::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip)
{
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
if (myStackSelection)
QToolTip::showText(helpEvent->globalPos(), myStackString.c_str());
else
QToolTip::hideText();
return true;
}
return QWidget::event(event);
}
void
MemViewWidget::resizeEvent(QResizeEvent *event)
{
QGLWidget::resizeEvent(event);
update();
}
QPoint
MemViewWidget::zoomPos(QPoint pos, int zoom) const
{
if (zoom < 0)
{
pos.rx() *= myImage.width();
pos.rx() /= width();
pos.ry() *= myImage.height();
pos.ry() /= height();
}
return pos;
}
void
MemViewWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
myStopWatch.start();
myDragging = true;
myDragRemainder = QPoint(0, 0);
myVelocity = std::queue<Velocity>();
}
myMousePos = event->pos();
}
void
MemViewWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
{
double time = myStopWatch.elapsed();
QPoint dir = myMousePos - event->pos();
panBy(dir);
if (myVelocity.size() >= 5)
myVelocity.pop();
myVelocity.push(Velocity(dir.x(), dir.y(), time));
}
myMousePos = event->pos();
update();
}
static const double theDragDelay = 0.1;
void
MemViewWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
myDragging = false;
// Compute the average velocity. We'll require at least 2 samples
// to avoid spikes. Only consider samples within a specified time
// interval.
int size = myVelocity.size();
double time = myStopWatch.elapsed() - theDragDelay;
Velocity vel(0, 0, 0);
while (myVelocity.size())
{
if (myVelocity.front().time > time)
vel += myVelocity.front();
else
size--;
myVelocity.pop();
}
if (size > 1)
{
vel *= 1.0 / theDragDelay;
myVelocity.push(vel);
}
myStopWatch.start();
}
}
void
MemViewWidget::wheelEvent(QWheelEvent *event)
{
const bool linear = myDisplay.getVisualization() == DisplayLayout::LINEAR;
const int inc = linear ? 1 : 2;
int zoom = myZoom;
if (event->delta() < 0)
{
if (zoom < 0)
zoom += 2;
else
zoom += inc;
}
else if (event->delta() > 0)
{
if (zoom <= 0)
zoom -= 2;
else
zoom -= inc;
}
changeZoom(zoom);
}
static void
minScroll(QScrollBar *scroll, int64 x, int64 size, bool zoomout)
{
int64 ox = x;
x += scroll->value();
if (zoomout)
{
x += 1;
x >>= 1;
}
else
{
x <<= 1;
size <<= 1;
}
x -= ox;
setScrollMax(scroll, size);
scroll->setValue(SYSclamp32(x));
}
static void
magScroll(QScrollBar *scroll, int64 x, int64 size, bool zoomout,
int64 winsize, int64 psize, int64 nsize)
{
if (zoomout)
{
x = (x*(psize+1)) / winsize;
x = scroll->value() - x;
}
else
{
x = (x*(nsize+1)) / winsize;
x += scroll->value();
}
setScrollMax(scroll, size);
scroll->setValue(SYSclamp32(x));
}
static void
magScrollLinear(QScrollBar *scroll, int64 x,
int64 winheight, int64 size,
int64 pwidth, int64 pheight,
int64 nwidth, int64 nheight)
{
x = (((x*pheight)/winheight + scroll->value())*pwidth)/nwidth -
(x*nheight)/winheight;
size = (size * pwidth) / nwidth;
setScrollMax(scroll, size);
scroll->setValue(SYSclamp32(x));
}
void
MemViewWidget::changeZoom(int zoom)
{
const bool linear = myDisplay.getVisualization() == DisplayLayout::LINEAR;
// Zoom in increments of 2 for block display
if (!linear)
zoom &= ~1;
// Zoom in increments of 2 for magnification
if (zoom < 0)
zoom = -((-zoom) & ~1);
zoom = SYSclamp(zoom, -16, 63-myState->getIgnoreBits());
if (zoom != myZoom)
{
if (zoom > 0)
{
myZoomState = new MemoryState(myState->getIgnoreBits()+zoom);
myZoomState->setSamplingInProgress();
myLoader->setZoomState(myZoomState);
}
else
{
myLoader->clearZoomState();
myZoomState = myState;
}
const bool zoomout = zoom > myZoom;
QPoint zpos = myMousePos;
if (zoom < 0 || myZoom < 0)
{
int64 pwidth = myImage.width();
int64 pheight = myImage.height();
// This makes a GL call
resizeImage(zoom);
if (!linear)
{
magScroll(myHScrollBar, zpos.x(), myDisplay.width(), zoomout,
width(), pwidth, myImage.width());
magScroll(myVScrollBar, zpos.y(), myDisplay.height(), zoomout,
height(), pheight, myImage.height());
}
else
{
magScrollLinear(myVScrollBar,
zpos.y(), height(), myDisplay.height(),
pwidth, pheight, myImage.width(), myImage.height());
}
}
else
{
if (!linear)
minScroll(myHScrollBar, zpos.x(), myDisplay.width(), zoomout);
minScroll(myVScrollBar, zpos.y(), myDisplay.height(), zoomout);
}
myZoom = zoom;
}
}
void
shortenDrag(double &val, double delta)
{
delta *= 2.0;
if (val > 0)
{
val -= delta*val;
val = SYSmax(val, 0.0);
}
else if (val < 0)
{
val -= delta*val;
val = SYSmin(val, 0.0);
}
}
void
MemViewWidget::timerEvent(QTimerEvent *event)
{
// Fast timer
if (event->timerId() == myFastTimer)
{
if (!myDragging && myVelocity.size())
{
Velocity &vel = myVelocity.front();
double time = myStopWatch.lap();
QPoint dir((int)(vel.x * time + 0.5F),
(int)(vel.y * time + 0.5F));
if (panBy(dir))
{
update();
}
shortenDrag(vel.x, time);
shortenDrag(vel.y, time);
}
}
else if (event->timerId() == mySlowTimer)
{
uint64 total_events = myLoader->getTotalEvents();
myEventInfo.sprintf("%lld events", total_events);
if (!myLoader->isComplete())
{
double time = myEventTimer.lap();
double rate = (total_events - myPrevEvents) / time;
QString str;
if (rate > 5e8)
str.sprintf(" (%.1fGev/s)", rate / 1e9);
else if (rate > 5e5)
str.sprintf(" (%.1fMev/s)", rate / 1e6);
else if (rate > 5e2)
str.sprintf(" (%.1fKev/s)", rate / 1e3);
else
str.sprintf(" (%.1fev/s)", rate);
myEventInfo.append(str);
myPrevEvents = total_events;
}
}
// This frequent status update seems to be fairly costly
QPoint pos = zoomPos(myMousePos, myZoom);
uint64 qaddr = myDisplay.queryPixelAddress(*myZoomState,
myHScrollBar->value() + pos.x(),
myVScrollBar->value() + pos.y());
QString message(myEventInfo);
myZoomState->appendAddressInfo(message, qaddr, *myMMapMap);
myStatusMessage->setText(message);
// Show the zoom level
QString zoominfo;
if (myZoom > 0)
zoominfo.sprintf("%.2gx", sqrt(1.0 / (1ull << myZoom)));
else
zoominfo.sprintf("%dx", 1 << (-myZoom >> 1));
myStatusZoom->setText(zoominfo);
// Find and stash the closest stack trace
uint64 off;
auto page = myZoomState->getPage(qaddr, off);
if (qaddr && page.exists() && page.state(off).time())
{
qaddr <<= myZoomState->getIgnoreBits();
StackTraceMapReader reader(*myStackTrace);
auto it = reader.findClosest(qaddr);
if (it == reader.end())
{
myStackSelection = 0;
}
else
{
myStackSelection = it.start();
myStackString = it.value().myStr;
}
}
else
{
myStackString = "";
myStackSelection = 0;
}
}
bool
MemViewWidget::panBy(QPoint dir)
{
myDragRemainder += dir;
QPoint zrem = zoomPos(myDragRemainder, myZoom);
myDragRemainder.rx() -= (zrem.x()*width())/myImage.width();
myDragRemainder.ry() -= (zrem.y()*height())/myImage.height();
myHScrollBar->setValue(myHScrollBar->value() + zrem.x());
myVScrollBar->setValue(myVScrollBar->value() + zrem.y());
return zrem.x() || zrem.y();
}