diff -r b67a6a72d7cd -r 3e977419b9c7 configs/example/fs.py --- a/configs/example/fs.py Sun Apr 10 18:49:58 2011 -0500 +++ b/configs/example/fs.py Sun Apr 10 18:50:03 2011 -0500 @@ -72,6 +72,9 @@ # System options parser.add_option("--kernel", action="store", type="string") parser.add_option("--script", action="store", type="string") +parser.add_option("--frame-capture", action="store_true", + help="Stores changed frame buffers from the VNC server to compressed "\ + "files") if buildEnv['TARGET_ISA'] == "arm": parser.add_option("--bare-metal", action="store_true", help="Provide the raw system without the linux specific bits") @@ -204,4 +207,7 @@ if options.timesync: root.time_sync_enable = True +if options.frame_capture: + VncServer.frame_capture = True + Simulation.run(options, root, test_sys, FutureClass) diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/bitmap.hh --- a/src/base/bitmap.hh Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/bitmap.hh Sun Apr 10 18:50:03 2011 -0500 @@ -62,6 +62,9 @@ */ Bitmap(VideoConvert::Mode mode, uint16_t w, uint16_t h, uint8_t *d); + /** Destructor */ + ~Bitmap(); + /** Provide the converter with the data that should be output. It will be * converted into rgb8888 and write out when write() is called. * @param d the data @@ -71,7 +74,18 @@ /** Write the provided data into the fstream provided * @param bmp stream to write to */ - void write(std::ostream *bmp); + void write(std::ostream *bmp) const; + + /** Gets a hash over the bitmap for quick comparisons to other bitmaps. + * + * If hashes from two bitmaps are different, the bitmaps are guaranteed to + * be different. However, hashes may be identical for two unique bitmaps + * though this should not be common. + * + * @return hash of the bitmap + */ + uint64_t getHash() const { return vc.getHash(data); } + private: VideoConvert::Mode mode; @@ -81,6 +95,9 @@ VideoConvert vc; + mutable char *headerBuffer; + static const size_t sizeofHeaderBuffer; + struct Magic { unsigned char magic_number[2]; diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/bitmap.cc --- a/src/base/bitmap.cc Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/bitmap.cc Sun Apr 10 18:50:03 2011 -0500 @@ -36,6 +36,7 @@ * * Authors: William Wang * Ali Saidi + * Chris Emmons */ #include @@ -43,29 +44,50 @@ #include "base/bitmap.hh" #include "base/misc.hh" +const size_t Bitmap::sizeofHeaderBuffer = sizeof(Magic) + sizeof(Header) + + sizeof(Info); + // bitmap class ctor Bitmap::Bitmap(VideoConvert::Mode _mode, uint16_t w, uint16_t h, uint8_t *d) : mode(_mode), height(h), width(w), data(d), - vc(mode, VideoConvert::rgb8888, width, height) + vc(mode, VideoConvert::rgb8888, width, height), headerBuffer(0) { } +Bitmap::~Bitmap() { + if (headerBuffer) + delete [] headerBuffer; +} + void -Bitmap::write(std::ostream *bmp) +Bitmap::write(std::ostream *bmp) const { assert(data); - // For further information see: http://en.wikipedia.org/wiki/BMP_file_format - Magic magic = {{'B','M'}}; - Header header = {sizeof(VideoConvert::Rgb8888) * width * height , 0, 0, 54}; - Info info = {sizeof(Info), width, height, 1, - sizeof(VideoConvert::Rgb8888) * 8, 0, - sizeof(VideoConvert::Rgb8888) * width * height, 1, 1, 0, 0}; + // header is always the same for a bitmap object; compute the info once per + // bitmap object + if (!headerBuffer) { + // For further information see: + // http://en.wikipedia.org/wiki/BMP_file_format + Magic magic = {{'B','M'}}; + Header header = {sizeof(VideoConvert::Rgb8888) * width * height, + 0, 0, 54}; + Info info = {sizeof(Info), width, height, 1, + sizeof(VideoConvert::Rgb8888) * 8, 0, + sizeof(VideoConvert::Rgb8888) * width * height, 1, 1, 0, 0}; - bmp->write(reinterpret_cast(&magic), sizeof(magic)); - bmp->write(reinterpret_cast(&header), sizeof(header)); - bmp->write(reinterpret_cast(&info), sizeof(info)); + char *p = headerBuffer = new char[sizeofHeaderBuffer]; + memcpy(p, reinterpret_cast(&magic), sizeof(Magic)); + p += sizeof(Magic); + memcpy(p, reinterpret_cast(&header), sizeof(Header)); + p += sizeof(Header); + memcpy(p, reinterpret_cast(&info), sizeof(Info)); + } + // 1. write the header + bmp->write(headerBuffer, sizeofHeaderBuffer); + + // 2. write the bitmap data uint8_t *tmp = vc.convert(data); uint32_t *tmp32 = (uint32_t*)tmp; @@ -79,4 +101,3 @@ delete [] tmp; } - diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/output.hh --- a/src/base/output.hh Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/output.hh Sun Apr 10 18:50:03 2011 -0500 @@ -26,6 +26,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: Nathan Binkert + * Chris Emmons */ #ifndef __BASE_OUTPUT_HH__ @@ -35,33 +36,142 @@ #include #include +/** Interface to creating files in an output directory from M5. */ class OutputDirectory { private: + /** File names and associated stream handles */ typedef std::map map_t; + /** Open file streams within this directory */ map_t files; + + /** Name of this directory */ std::string dir; + /** System-specific path separator character */ + static const char PATH_SEPARATOR = '/'; + + /** Returns relative file names prepended with name of this directory. + * + * Returns absolute file names unaltered. + * + * @param name file name to prepend with directory name + * @return file name prepended with base directory name or unaltered + * absolute file name + */ std::string resolve(const std::string &name) const; protected: + /** Determines whether given file name corresponds to standard output + * streams. + * @param name name of file to check + * @return output stream for standard output or error stream if name + * corresponds to one or the other; NULL otherwise + */ std::ostream *checkForStdio(const std::string &name) const; + + /** Opens a file (optionally compressed). + * + * Will open a file as a compressed stream if filename ends in .gz. + * + * @param filename file to open + * @param mode attributes to open file with + * @return stream pointer to opened file; will cause sim fail on error + */ std::ostream *openFile(const std::string &filename, - std::ios_base::openmode mode = std::ios::trunc) const; + std::ios_base::openmode mode = std::ios::trunc); public: + /** Constructor. */ OutputDirectory(); + + /** Destructor. */ ~OutputDirectory(); + /** Sets name of this directory. + * @param dir name of this directory + */ void setDirectory(const std::string &dir); + + /** Gets name of this directory. + * @return name of this directory + */ const std::string &directory() const; + /** Creates a file in this directory (optionally compressed). + * + * Will open a file as a compressed stream if filename ends in .gz. + * + * @param name name of file to create (without this directory's name + * leading it) + * @param binary true to create a binary file; false otherwise + * @return stream to the opened file + */ std::ostream *create(const std::string &name, bool binary = false); + + /** Closes a file stream. + * + * Stream must have been opened through this interface, or sim will fail. + * + * @param openStream open stream to close + */ + void close(std::ostream *openStream); + + /** Finds stream associated with a file or creates new file if it doesn't + * exist. + * + * @param name name of file to find stream for; if file doesn't exist, + * attempts to create a new file + * @return stream to specified file + */ std::ostream *find(const std::string &name); + /** Finds stream associated with a file. + * @param name of file + * @return stream to specified file or NULL if file does not exist + */ + std::ostream *find(const std::string &name) const; + + /** Returns true if stream is open and not standard output or error. + * @param os output stream to evaluate + * @return true if os is non-NULL and not cout or cerr + */ static bool isFile(const std::ostream *os); + + /** Determines whether a file name corresponds to a file in this directory. + * @param name name of file to evaluate + * @return true iff file has been opened in this directory or exists on the + * file system within this directory + */ + bool isFile(const std::string &name) const; + + /** Returns true if stream is open and not standard output or error. + * @param os output stream to evaluate + * @return true if os is non-NULL and not cout or cerr + */ static inline bool isFile(const std::ostream &os) { return isFile(&os); } + + /** Creates a subdirectory within this directory. + * @param name name of subdirectory + * @return the new subdirectory's name suffixed with a path separator + */ + std::string createSubdirectory(const std::string &name) const; + + /** Removes a specified file or subdirectory. + * + * Will cause sim to fail for most errors. However, it will only warn the + * user if a directory could not be removed. This is in place to + * accommodate slow file systems where file deletions within a subdirectory + * may not be recognized quickly enough thereby causing the subsequent call + * to remove the directory to fail (seemingly unempty directory). + * + * @param name name of file or subdirectory to remove; name should not + * be prepended with the name of this directory object + * @param recursive set to true to attempt to recursively delete a + * subdirectory and its contents + */ + void remove(const std::string &name, bool recursive=false); }; extern OutputDirectory simout; diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/output.cc --- a/src/base/output.cc Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/output.cc Sun Apr 10 18:50:03 2011 -0500 @@ -28,7 +28,9 @@ * Authors: Nathan Binkert */ +#include #include +#include #include #include #include @@ -45,7 +47,7 @@ OutputDirectory simout; -/** +/** @file This file manages creating/deleting output files for the simulator. * */ OutputDirectory::OutputDirectory() @@ -73,26 +75,51 @@ ostream * OutputDirectory::openFile(const string &filename, - ios_base::openmode mode) const + ios_base::openmode mode) { if (filename.find(".gz", filename.length()-3) < filename.length()) { ogzstream *file = new ogzstream(filename.c_str(), mode); - if (!file->is_open()) fatal("Cannot open file %s", filename); - + files[filename] = file; return file; } else { ofstream *file = new ofstream(filename.c_str(), mode); - if (!file->is_open()) fatal("Cannot open file %s", filename); - + files[filename] = file; return file; } } void +OutputDirectory::close(ostream *openStream) { + map_t::iterator i; + for (i = files.begin(); i != files.end(); i++) { + if (i->second == openStream) { + ofstream *fs = dynamic_cast(i->second); + if (fs) { + fs->close(); + delete i->second; + break; + } else { + ogzstream *gfs = dynamic_cast(i->second); + if (gfs) { + gfs->close(); + delete i->second; + break; + } + } + } + } + + if (i == files.end()) + fatal("Attempted to close an unregistred file stream"); + + files.erase(i); +} + +void OutputDirectory::setDirectory(const string &d) { if (!dir.empty()) @@ -100,9 +127,9 @@ dir = d; - // guarantee that directory ends with a '/' - if (dir[dir.size() - 1] != '/') - dir += "/"; + // guarantee that directory ends with a path separator + if (dir[dir.size() - 1] != PATH_SEPARATOR) + dir += PATH_SEPARATOR; } const string & @@ -117,7 +144,7 @@ inline string OutputDirectory::resolve(const string &name) const { - return (name[0] != '/') ? dir + name : name; + return (name[0] != PATH_SEPARATOR) ? dir + name : name; } ostream * @@ -142,8 +169,8 @@ if (file) return file; - string filename = resolve(name); - map_t::iterator i = files.find(filename); + const string filename = resolve(name); + map_t::const_iterator i = files.find(filename); if (i != files.end()) return (*i).second; @@ -152,8 +179,102 @@ return file; } +ostream * +OutputDirectory::find(const string &name) const +{ + ostream *file = checkForStdio(name); + if (file) + return file; + + const string filename = resolve(name); + map_t::const_iterator i = files.find(filename); + if (i != files.end()) + return (*i).second; + + return NULL; +} + bool OutputDirectory::isFile(const std::ostream *os) { return os && os != &cerr && os != &cout; } + +bool +OutputDirectory::isFile(const string &name) const +{ + // definitely a file if in our data structure + if (find(name) != NULL) return true; + + struct stat st_buf; + int st = stat(name.c_str(), &st_buf); + return (st == 0) && S_ISREG(st_buf.st_mode); +} + +string +OutputDirectory::createSubdirectory(const string &name) const +{ + const string new_dir = resolve(name); + if (new_dir.find(directory()) == string::npos) + fatal("Attempting to create subdirectory not in m5 output dir\n"); + + // if it already exists, that's ok; otherwise, fail if we couldn't create + if ((mkdir(new_dir.c_str(), 0755) != 0) && (errno != EEXIST)) + fatal("Failed to create new output subdirectory '%s'\n", new_dir); + + return name + PATH_SEPARATOR; +} + +void +OutputDirectory::remove(const string &name, bool recursive) +{ + const string fname = resolve(name); + + if (fname.find(directory()) == string::npos) + fatal("Attempting to remove file/dir not in output dir\n"); + + if (isFile(fname)) { + // close and release file if we have it open + map_t::iterator itr = files.find(fname); + if (itr != files.end()) { + delete itr->second; + files.erase(itr); + } + + if (::remove(fname.c_str()) != 0) + fatal("Could not erase file '%s'\n", fname); + } else { + // assume 'name' is a directory + if (recursive) { + DIR *dir = opendir(fname.c_str()); + + // silently ignore removal request for non-existent directory + if ((!dir) && (errno == ENOENT)) + return; + + // fail on other errors + if (!dir) { + perror("opendir"); + fatal("Error opening directory for recursive removal '%s'\n", + fname); + } + + struct dirent *de = readdir(dir); + while (de != NULL) { + // ignore files starting with a '.'; user must delete those + // manually if they really want to + if (de->d_name[0] != '.') + remove(name + PATH_SEPARATOR + de->d_name, recursive); + + de = readdir(dir); + } + } + + // try to force recognition that we deleted the files in the directory + sync(); + + if (::remove(fname.c_str()) != 0) { + perror("Warning! 'remove' failed. Could not erase directory."); + } + } +} diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/vnc/VncServer.py --- a/src/base/vnc/VncServer.py Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/vnc/VncServer.py Sun Apr 10 18:50:03 2011 -0500 @@ -43,3 +43,4 @@ type = 'VncServer' port = Param.TcpPort(5900, "listen port") number = Param.Int(0, "vnc client number") + frame_capture = Param.Bool(False, "capture changed frames to files") diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/vnc/convert.hh --- a/src/base/vnc/convert.hh Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/vnc/convert.hh Sun Apr 10 18:50:03 2011 -0500 @@ -107,12 +107,23 @@ * @param fb the frame buffer to convert * @return the converted data (user must free) */ - uint8_t* convert(uint8_t *fb); + uint8_t* convert(const uint8_t *fb) const; /** Return the number of pixels that this buffer specifies * @return number of pixels */ - int area() { return width * height; } + int area() const { return width * height; } + + /** + * Returns a hash on the raw data. + * + * If hashes from two buffers are different, the buffers are guaranteed to + * be different. However, hashes may be identical for two unique buffers + * though this should not be common. + * + * @return hash of the buffer + */ + uint64_t getHash(const uint8_t *fb) const; private: @@ -121,7 +132,7 @@ * @param fb the data to convert * @return converted data */ - uint8_t* bgr8888rgb8888(uint8_t *fb); + uint8_t* bgr8888rgb8888(const uint8_t *fb) const; /** * Convert a bgr565 or rgb565 input to rgb8888. @@ -129,7 +140,7 @@ * @param bgr true if the input data is bgr565 * @return converted data */ - uint8_t* m565rgb8888(uint8_t *fb, bool bgr); + uint8_t* m565rgb8888(const uint8_t *fb, bool bgr) const; Mode inputMode; Mode outputMode; diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/vnc/convert.cc --- a/src/base/vnc/convert.cc Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/vnc/convert.cc Sun Apr 10 18:50:03 2011 -0500 @@ -67,7 +67,7 @@ } uint8_t* -VideoConvert::convert(uint8_t *fb) +VideoConvert::convert(const uint8_t *fb) const { switch (inputMode) { case bgr565: @@ -81,8 +81,25 @@ } } +uint64_t +VideoConvert::getHash(const uint8_t *fb) const { + const uint8_t *fb_e = fb + area(); + + uint64_t hash = 1; + while (fb < fb_e - 8) { + hash += *((const uint64_t*)fb); + fb += 8; + } + + while (fb < fb_e) { + hash += *(fb++); + } + + return hash; +} + uint8_t* -VideoConvert::m565rgb8888(uint8_t *fb, bool bgr) +VideoConvert::m565rgb8888(const uint8_t *fb, bool bgr) const { uint8_t *out = new uint8_t[area() * sizeof(uint32_t)]; uint32_t *out32 = (uint32_t*)out; @@ -113,7 +130,7 @@ uint8_t* -VideoConvert::bgr8888rgb8888(uint8_t *fb) +VideoConvert::bgr8888rgb8888(const uint8_t *fb) const { uint8_t *out = new uint8_t[area() * sizeof(uint32_t)]; uint32_t *out32 = (uint32_t*)out; diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/vnc/vncserver.hh --- a/src/base/vnc/vncserver.hh Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/vnc/vncserver.hh Sun Apr 10 18:50:03 2011 -0500 @@ -55,6 +55,9 @@ #include "sim/sim_object.hh" #include "params/VncServer.hh" +class Bitmap; + + /** * A device that expects to receive input from the vnc server should derrive * (through mulitple inheritence if necessary from VncKeyboard or VncMouse @@ -316,7 +319,25 @@ /** The video converter that transforms data for us */ VideoConvert *vc; + /** Whether to capture snapshots of frame buffer or not */ + bool frameCapture; + + /** Current frame being captured to file */ + int captureCurrentFrame; + + /** Directory to store captured frames to */ + std::string captureOutputDirectory; + + /** Computed hash of the last captured frame */ + uint64_t captureLastHash; + + /** Cached bitmap object for writing out frame buffers to file */ + Bitmap *captureBitmap; + protected: + /** Captures the current frame buffer to a file */ + void captureFrameBuffer(); + /** * vnc client Interface */ @@ -449,6 +470,8 @@ setDirty() { sendUpdate = true; + if (frameCapture) + captureFrameBuffer(); sendFrameBufferUpdate(); } diff -r b67a6a72d7cd -r 3e977419b9c7 src/base/vnc/vncserver.cc --- a/src/base/vnc/vncserver.cc Sun Apr 10 18:49:58 2011 -0500 +++ b/src/base/vnc/vncserver.cc Sun Apr 10 18:50:03 2011 -0500 @@ -45,13 +45,20 @@ #include #include +#include #include +#include #include +#include #include #include +#include + #include "base/atomicio.hh" +#include "base/bitmap.hh" #include "base/misc.hh" +#include "base/output.hh" #include "base/socket.hh" #include "base/trace.hh" #include "base/vnc/vncserver.hh" @@ -97,7 +104,8 @@ : SimObject(p), listenEvent(NULL), dataEvent(NULL), number(p->number), dataFd(-1), _videoWidth(1), _videoHeight(1), clientRfb(0), keyboard(NULL), mouse(NULL), sendUpdate(false), videoMode(VideoConvert::UnknownMode), - vc(NULL) + vc(NULL), frameCapture(p->frame_capture), captureCurrentFrame(0), + captureLastHash(0), captureBitmap(0) { if (p->port) listen(p->port); @@ -121,6 +129,15 @@ pixelFormat.blueshift = 0; + if (frameCapture) { + // remove existing frame output directory if it exists, then create a + // clean empty directory + const string FRAME_OUTPUT_SUBDIR = "frames_" + name(); + simout.remove(FRAME_OUTPUT_SUBDIR, true); + captureOutputDirectory = simout.createSubdirectory( + FRAME_OUTPUT_SUBDIR); + } + DPRINTF(VNC, "Vnc server created at port %d\n", p->port); } @@ -685,6 +702,16 @@ vc = new VideoConvert(mode, VideoConvert::rgb8888, videoWidth(), videoHeight()); + if (frameCapture) { + // create bitmap of the frame with new attributes + if (captureBitmap) + delete captureBitmap; + + assert(clientRfb); + captureBitmap = new Bitmap(videoMode, width, height, clientRfb); + assert(captureBitmap); + } + if (dataFd > 0 && clientRfb && curState == NormalPhase) { if (supportsResizeEnc) sendFrameBufferResized(); @@ -701,3 +728,27 @@ { return new VncServer(this); } + +void +VncServer::captureFrameBuffer() { + assert(captureBitmap); + + // skip identical frames + uint64_t new_hash = captureBitmap->getHash(); + if (captureLastHash == new_hash) + return; + captureLastHash = new_hash; + + // get the filename for the current frame + char frameFilenameBuffer[32]; + snprintf(frameFilenameBuffer, 32, "fb.%06d.bmp.gz", captureCurrentFrame); + const string frameFilename(frameFilenameBuffer); + + // create the compressed framebuffer file + ostream *fb_out = simout.create(captureOutputDirectory + frameFilename, + true); + captureBitmap->write(fb_out); + simout.close(fb_out); + + ++captureCurrentFrame; +}