Path: blob/master/runtime/gc_verbose_old/VerboseFileLoggingOutput.cpp
5985 views
1/*******************************************************************************2* Copyright (c) 1991, 2019 IBM Corp. and others3*4* This program and the accompanying materials are made available under5* the terms of the Eclipse Public License 2.0 which accompanies this6* distribution and is available at https://www.eclipse.org/legal/epl-2.0/7* or the Apache License, Version 2.0 which accompanies this distribution and8* is available at https://www.apache.org/licenses/LICENSE-2.0.9*10* This Source Code may also be made available under the following11* Secondary Licenses when the conditions for such availability set12* forth in the Eclipse Public License, v. 2.0 are satisfied: GNU13* General Public License, version 2 with the GNU Classpath14* Exception [1] and GNU General Public License, version 2 with the15* OpenJDK Assembly Exception [2].16*17* [1] https://www.gnu.org/software/classpath/license.html18* [2] http://openjdk.java.net/legal/assembly-exception.html19*20* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception21*******************************************************************************/2223#include "modronnls.h"2425#include "VerboseFileLoggingOutput.hpp"26#include "VerboseEvent.hpp"27#include "GCExtensions.hpp"28#include "VerboseBuffer.hpp"29#include "EnvironmentBase.hpp"3031#include <string.h>3233/**34* Create a new MM_VerboseFileLoggingOutput instance.35* @return Pointer to the new MM_VerboseFileLoggingOutput.36*/37MM_VerboseFileLoggingOutput *38MM_VerboseFileLoggingOutput::newInstance(MM_EnvironmentBase *env, char *filename, UDATA numFiles, UDATA numCycles)39{40MM_GCExtensions *extensions = MM_GCExtensions::getExtensions(env->getOmrVM());4142MM_VerboseFileLoggingOutput *agent = (MM_VerboseFileLoggingOutput *)extensions->getForge()->allocate(sizeof(MM_VerboseFileLoggingOutput), MM_AllocationCategory::DIAGNOSTIC, J9_GET_CALLSITE());43if(agent) {44new(agent) MM_VerboseFileLoggingOutput(env);45if(!agent->initialize(env, filename, numFiles, numCycles)) {46agent->kill(env);47agent = NULL;48}49}50return agent;51}5253/**54* Initializes the MM_VerboseFileLoggingOutput instance.55* @return true on success, false otherwise56*/57bool58MM_VerboseFileLoggingOutput::initialize(MM_EnvironmentBase *env, const char *filename, UDATA numFiles, UDATA numCycles)59{60_numFiles = numFiles;61_numCycles = numCycles;6263if((_numFiles > 0) && (_numCycles > 0)) {64_mode = rotating_files;65} else {66_mode = single_file;67}6869if (!initializeTokens(env)) {70return false;71}7273if (!initializeFilename(env, filename)) {74return false;75}7677IDATA initialFile = findInitialFile(env);78if (initialFile < 0) {79return false;80}81_currentFile = initialFile;8283if(!openFile(env)) {84return false;85}8687if(NULL == (_buffer = MM_VerboseBuffer::newInstance(env, INITIAL_BUFFER_SIZE))) {88return false;89}9091return true;92}9394/**95* Tear down the structures managed by the MM_VerboseFileLoggingOutput.96* Tears down the verbose buffer.97*/98void99MM_VerboseFileLoggingOutput::tearDown(MM_EnvironmentBase *env)100{101PORT_ACCESS_FROM_ENVIRONMENT(env);102MM_GCExtensions *extensions = MM_GCExtensions::getExtensions(env->getOmrVM());103104if(NULL != _buffer) {105_buffer->kill(env);106}107108j9str_free_tokens(_tokens);109extensions->getForge()->free(_filename);110}111112/**113* Initialize the _tokens field.114* for backwards compatibility with Sovereign, alias %p to be the same as %pid115*/116bool117MM_VerboseFileLoggingOutput::initializeTokens(MM_EnvironmentBase *env)118{119PORT_ACCESS_FROM_ENVIRONMENT(env);120char pidBuffer[64];121122_tokens = j9str_create_tokens(j9time_current_time_millis());123if (_tokens == NULL) {124return false;125}126127if (sizeof(pidBuffer) < j9str_subst_tokens(pidBuffer, sizeof(pidBuffer), "%pid", _tokens)) {128return false;129}130131if (j9str_set_token(PORTLIB, _tokens, "p", "%s", pidBuffer)) {132return false;133}134135return true;136}137138/**139* Initialize the _filename field based on filename.140*141* Since token substitution only supports tokens starting with %, all # characters in the142* filename will be replaced with %seq (unless the # is already preceded by an odd number143* of % signs, in which case it is replaced with seq).144*145* e.g. foo# --> foo%seq146* foo%# --> foo%seq147* foo%%# --> foo%%%seq148*149* If %seq or %# is not specified, and if rotating logs have been requested, ".%seq" is150* appended to the log name.151*152* If the resulting filename is too large to fit in the buffer, it is truncated.153*154* @param[in] env the current environment155* @param[in] filename the user specified filename156*157* @return true on success, false on failure158*/159bool160MM_VerboseFileLoggingOutput::initializeFilename(MM_EnvironmentBase *env, const char *filename)161{162MM_GCExtensions *extensions = MM_GCExtensions::getExtensions(env->getOmrVM());163164if (_mode == rotating_files) {165const char* read = filename;166167/* count the number of hashes in the source string */168UDATA hashCount = 0;169for (read = filename; '\0' != *read; read++) {170if ('#' == *read) {171hashCount++;172}173}174175/* allocate memory for the copied template filename */176UDATA nameLen = strlen(filename) + 1;177if (hashCount > 0) {178/* each # expands into %seq, so for each # add 3 to len */179nameLen += hashCount * (sizeof("seq") - 1);180} else {181/* if there are no hashes we may append .%seq to the end */182nameLen += sizeof(".%seq") - 1;183}184185_filename = (char*)extensions->getForge()->allocate(nameLen, MM_AllocationCategory::DIAGNOSTIC, J9_GET_CALLSITE());186if (NULL == _filename) {187return false;188}189190/* copy the original filename into the allocated memory, expanding #s to %seq */191bool foundSeq = false;192bool oddPercents = false;193char* write = _filename;194for (read = filename; '\0' != *read; read++) {195/* check to see if %seq appears in the source filename */196if (oddPercents && (0 == strncmp(read, "seq", 3))) {197foundSeq = true;198}199200if ('#' == *read) {201strcpy(write, oddPercents ? "seq" : "%seq");202write += strlen(write);203} else {204*write++ = *read;205}206oddPercents = ('%' == *read) ? !oddPercents : false;207}208209*write = '\0';210211if ( (false == foundSeq) && (0 == hashCount) ) {212strcpy(write, ".%seq");213}214} else {215_filename = (char*)extensions->getForge()->allocate(strlen(filename) + 1, MM_AllocationCategory::DIAGNOSTIC, J9_GET_CALLSITE());216if (NULL == _filename) {217return false;218}219strcpy(_filename, filename);220}221222return true;223}224225/**226* Formats and output data.227* Called by events, formats the data passed into verbose form and outputs it.228* @param indent The current level of indentation.229* @param format String to format the data into.230*/231void232MM_VerboseFileLoggingOutput::formatAndOutput(J9VMThread *vmThread, UDATA indent, const char *format, ...)233{234char inputString[VGC_INPUT_STRING_SIZE];235char localBuf[VGC_INPUT_STRING_SIZE];236UDATA length;237va_list args;238239MM_EnvironmentBase* env = MM_EnvironmentBase::getEnvironment((OMR_VMThread *)vmThread->omrVMThread);240PORT_ACCESS_FROM_ENVIRONMENT(env);241242localBuf[0] = '\0';243for(UDATA i=0; i < indent; i++) {244strcat(localBuf, VGC_INDENT_SPACER);245}246247va_start(args, format);248j9str_vprintf(inputString, VGC_INPUT_STRING_SIZE - strlen(localBuf), format, args);249va_end(args);250251strcat(localBuf, inputString);252strcat(localBuf, "\n");253length = strlen(localBuf);254255if(NULL != _buffer) {256if(_buffer->add(env, localBuf)) {257/* Added successfully - return */258return;259}260}261262/* If there's a problem with the buffering, we just write straight to the file or stderr */263if(-1 != _logFileDescriptor) {264j9file_write_text(_logFileDescriptor, localBuf, length);265} else {266j9file_write_text(J9PORT_TTY_ERR, localBuf, length);267}268}269270/**271* Generate an expanded filename based on currentFile.272* The caller is responsible for freeing the returned memory.273*274* @param env the current thread275* @param currentFile the current file number to substitute into the filename template276*277* @return NULL on failure, allocated memory on success278*/279char*280MM_VerboseFileLoggingOutput::expandFilename(MM_EnvironmentBase *env, UDATA currentFile)281{282PORT_ACCESS_FROM_ENVIRONMENT(env);283MM_GCExtensions *extensions = MM_GCExtensions::getExtensions(env->getOmrVM());284285if (_mode == rotating_files) {286j9str_set_token(PORTLIB, _tokens, "seq", "%03zu", currentFile + 1); /* plus one so the filenames start from .001 instead of .000 */287}288289UDATA len = j9str_subst_tokens(NULL, 0, _filename, _tokens);290char *filenameToOpen = (char*)extensions->getForge()->allocate(len, MM_AllocationCategory::DIAGNOSTIC, J9_GET_CALLSITE());291if (NULL != filenameToOpen) {292j9str_subst_tokens(filenameToOpen, len, _filename, _tokens);293}294return filenameToOpen;295296}297298/**299* Probe the file system for existing files. Determine300* the first number which is unused, or the number of the oldest301* file if all numbers are used.302* @return the first file number to use (starting at 0), or -1 on failure303*/304IDATA305MM_VerboseFileLoggingOutput::findInitialFile(MM_EnvironmentBase *env)306{307PORT_ACCESS_FROM_ENVIRONMENT(env);308MM_GCExtensions *extensions = MM_GCExtensions::getExtensions(env->getOmrVM());309I_64 oldestTime = J9CONST64(0x7FFFFFFFFFFFFFFF); /* the highest possible time. */310IDATA oldestFile = 0;311312if (_mode != rotating_files) {313/* nothing to do */314return 0;315}316317for (UDATA currentFile = 0; currentFile < _numFiles; currentFile++) {318char *filenameToOpen = expandFilename(env, currentFile);319if (NULL == filenameToOpen) {320return -1;321}322323I_64 thisTime = j9file_lastmod(filenameToOpen);324extensions->getForge()->free(filenameToOpen);325326if (thisTime < 0) {327/* file doesn't exist, or some other problem reading the file */328oldestFile = currentFile;329break;330} else if (thisTime < oldestTime) {331oldestTime = thisTime;332oldestFile = currentFile;333}334}335336return oldestFile;337}338339/**340* Opens the file to log output to and prints the header.341* @return true on success, false otherwise342*/343bool344MM_VerboseFileLoggingOutput::openFile(MM_EnvironmentBase *env)345{346PORT_ACCESS_FROM_ENVIRONMENT(env);347J9JavaVM* javaVM = (J9JavaVM *)env->getOmrVM()->_language_vm;348MM_GCExtensions* extensions = MM_GCExtensions::getExtensions(javaVM);349const char* version = javaVM->memoryManagerFunctions->omrgc_get_version(env->getOmrVM());350351char *filenameToOpen = expandFilename(env, _currentFile);352if (NULL == filenameToOpen) {353return false;354}355356_logFileDescriptor = j9file_open(filenameToOpen, EsOpenRead | EsOpenWrite | EsOpenCreate | EsOpenTruncate, 0666);357if(-1 == _logFileDescriptor) {358char *cursor = filenameToOpen;359/**360* This may have failed due to directories in the path not being available.361* Try to create these directories and attempt to open again before failing.362*/363while ( (cursor = strchr(++cursor, DIR_SEPARATOR)) != NULL ) {364*cursor = '\0';365j9file_mkdir(filenameToOpen);366*cursor = DIR_SEPARATOR;367}368369/* Try again */370_logFileDescriptor = j9file_open(filenameToOpen, EsOpenRead | EsOpenWrite | EsOpenCreate | EsOpenTruncate, 0666);371if (-1 == _logFileDescriptor) {372j9nls_printf(PORTLIB, J9NLS_ERROR, J9NLS_GC_UNABLE_TO_OPEN_FILE, filenameToOpen);373extensions->getForge()->free(filenameToOpen);374return false;375}376}377378extensions->getForge()->free(filenameToOpen);379380j9file_printf(PORTLIB, _logFileDescriptor, VERBOSEGC_HEADER_TEXT_ALL, version);381382return true;383}384385/**386* Prints the footer and closes the file being logged to.387*/388void389MM_VerboseFileLoggingOutput::closeFile(MM_EnvironmentBase *env)390{391PORT_ACCESS_FROM_ENVIRONMENT(env);392393if(-1 != _logFileDescriptor){394UDATA length = strlen(VERBOSEGC_FOOTER_TEXT "\n");395j9file_write_text(_logFileDescriptor, VERBOSEGC_FOOTER_TEXT "\n", length);396j9file_close(_logFileDescriptor);397_logFileDescriptor = -1;398}399}400401/**402* Closes the agent's output stream.403*/404void405MM_VerboseFileLoggingOutput::closeStream(MM_EnvironmentBase *env)406{407closeFile(env);408}409410/**411* Flushes the verbose buffer to the output stream.412* Also cycles the output files if necessary.413*/414void415MM_VerboseFileLoggingOutput::endOfCycle(J9VMThread *vmThread)416{417MM_EnvironmentBase *env = MM_EnvironmentBase::getEnvironment((OMR_VMThread *)vmThread->omrVMThread);418PORT_ACCESS_FROM_ENVIRONMENT(env);419420if(-1 == _logFileDescriptor) {421/* we open the file at the end of the cycle so can't have a final empty file at the end of a run */422openFile(env);423}424425if(NULL != _buffer) {426if(-1 != _logFileDescriptor){427j9file_write_text(_logFileDescriptor, _buffer->contents(), _buffer->currentSize());428j9file_write_text(_logFileDescriptor, "\n", 1);429} else {430j9file_write_text(J9PORT_TTY_ERR, _buffer->contents(), _buffer->currentSize());431j9file_write_text(J9PORT_TTY_ERR, "\n", 1);432}433_buffer->reset();434}435436if(rotating_files == _mode) {437_currentCycle = (_currentCycle + 1) % _numCycles;438if(0 == _currentCycle) {439closeFile(env);440_currentFile = (_currentFile + 1) % _numFiles;441}442}443}444445/**446* Reconfigures the agent according to the parameters passed.447* Required for Dynamic verbose gc configuration.448* @param filename The name of the file or output stream to log to.449* @param fileCount The number of files to log to.450* @param iterations The number of gc cycles to log to each file.451*/452bool453MM_VerboseFileLoggingOutput::reconfigure(MM_EnvironmentBase *env, const char *filename, UDATA numFiles, UDATA numCycles)454{455closeFile(env);456return initialize(env, filename, numFiles, numCycles);457}458459460