#!/usr/bin/python """ pg_generate_conf Sample usage shown by running with "--help" """ import sys import os import datetime import optparse class PGConfigLine: """ Stores the value of a single line in the postgresql.conf file, with the following fields: lineNumber : integer originalLine : string commentSection : string setsParameter : boolean If setsParameter is True these will also be set: name : string readable : string raw : string This is the actual value delimiter (expectations are ' and ") """ def __init__(self,line,num=0): self.originalLine=line self.lineNumber=num self.setsParameter=False # Remove comments and edge whitespace self.commentSection="" commentIndex=line.find('#') if commentIndex >= 0: line=line[0:commentIndex] self.commentSection=line[commentIndex:] line=line.strip() if line == "": return # Split into name,value pair equalIndex=line.find('=') if equalIndex<0: return (name,value)=line.split('=') name=name.strip() value=value.strip() self.name=name; self.setsParameter=True; # Many types of values have ' ' characters around them, strip # TODO Set delimiter based on whether there is one here or not value=value.rstrip("'") value=value.lstrip("'") self.readable=value def outputFormat(self): s=self.originalLine; return s # Implement a Java-ish interface for this class def getName(self): return self.name def getValue(self): return self.readable def getLineNumber(self): return self.lineNumber def isSetting(self): return self.setsParameter def toString(self): s=str(self.lineNumber)+" sets?="+str(self.setsParameter) if self.setsParameter: s=s+" "+self.getName()+"="+self.getValue() # TODO: Include commentSection, readable,raw, delimiter s=s+" originalLine: "+self.originalLine return s class PGConfigFile: """ Read, write, and manage a postgresql.conf file There are two main structures here: configFile[]: Array of PGConfigLine entries for each line in the file settingLookup: Dictionary mapping parameter names to the line that set them """ def __init__(self, filename): self.readConfigFile(filename) def readConfigFile(self,filename): self.filename=filename self.settingsLookup={} self.configFile=[] lineNum=0; for line in open(filename): line=line.rstrip('\n') lineNum=lineNum + 1 configLine=PGConfigLine(line,lineNum) self.configFile.append(configLine) if configLine.isSetting(): self.settingsLookup[configLine.getName()]=configLine def updateSetting(self,name,newValue): newLineText=str(name)+" = "+str(newValue)+" # pg_generate_conf wizard "+str(datetime.date.today()) newLine=PGConfigLine(newLineText) if self.settingsLookup.has_key(name): # Comment out old line oldLine=self.settingsLookup[name] oldLineNum=oldLine.getLineNumber() commentedLineText="# "+oldLine.outputFormat() commentedLine=PGConfigLine(commentedLineText,oldLineNum) # Subtract one here to adjust for zero offset of array. # Any future change that adds lines in-place will need to do something # smarter here, because the line numbers won't match the array indexes # anymore self.configFile[oldLineNum-1]=commentedLine self.configFile.append(newLine) self.settingsLookup[name]=newLine def updateIfLarger(self,name,newValue): if self.settingsLookup.has_key(name): # TODO This comparison needs all the values converted to numeric form # and converted to the same scale before it will work if (True): #newValue > self.settingsLookup[name].getValue(): self.updateSetting(name,newValue) def writeConfigFile(self,fileHandle): for l in self.configFile: fileHandle.write(l.outputFormat()+"\n") def debugPrintInput(self): print "Original file:" for l in self.configFile: print l.toString() def debugPrintSettings(self): print "Settings listing:" for k in self.settingsLookup.keys(): print k,'=',self.settingsLookup[k].getValue() def totalMem(): # Should work on UNIX and Mac OS platforms physPages = os.sysconf("SC_PHYS_PAGES") pageSize = os.sysconf("SC_PAGE_SIZE") totalMem = physPages * pageSize return totalMem def ReadOptions(): parser=optparse.OptionParser( usage="usage: %prog [options]", version="1.0", conflict_handler="resolve") parser.add_option('-i','--input-config',dest="inputConfig",default=None, help="Input configuration file") parser.add_option('-o','--output-config',dest="outputConfig",default=None, help="Output configuration file, defaults to standard output") parser.add_option('-M','--memory',dest="totalMemory",default=None, help="Total system memory, will attempt to detect if unspecified") parser.add_option('-T','--type',dest="dbType",default="Mixed", help="Database type, defaults to Mixed, valid options are DSS, OLTP, Web, Mixed, Desktop") parser.add_option('-c','--connections',dest="connections",default=None, help="Maximum number of expected connections, default depends on database type") parser.add_option('-D','--debug',action="store_true",dest="debug", default="False",help="Enable debugging mode") (options,args)=parser.parse_args() if options.debug==True: print "Command line options: ",options print "Command line arguments: ",args return (options,args) def wizardTune(config,options): # We expect the following options are passed into here: # # dbType: Defaults to Mixed # connections: If missing, will set based on dbType # totalMemory: If missing, will detect dbType=options.dbType.lower() if dbType=="mixed": rconn=100 minconn=50 elif dbType=="dss": rconn=10 minconn=10 elif dbType=="web": rconn=300 minconn=100 elif dbType=="oltp": rconn=200 minconn=50 elif dbType=="desktop": rconn=10 minconn=5 else: print "Error: unexpected setting for dbType" os.exit(1) if options.connections==None: connections=rconn else: connections=options.connections if options.totalMemory==None: totalMemory=totalMem() else: totalMemory=options.totalMemory mb=1024*1024 osOverhead=256 * mb; memAvail=totalMemory - osOverhead; if memAvail>=(osOverhead / 2): # The net effect of the above is that if you don't have at least 384MB # of physical memory, we don't do any tuning suggestions right now. # The simple formulas below presume a relatively modern system with at # least that much RAM available sharedMB=(memAvail / 4) / mb; cacheSizeMB=(memAvail * 3 / 4) / mb; config.updateIfLarger('shared_buffers',"%d" % sharedMB+"MB") config.updateIfLarger('effective_cache_size',"%d" % cacheSizeMB+"MB") config.updateSetting('max_connections',connections) if __name__=='__main__': (options,args)=ReadOptions() configFile=options.inputConfig if configFile==None: print "Can't do anything without an input config file; try --help" sys.exit(1) # TODO Show usage here config=PGConfigFile(configFile) if (options.debug==True): config.debugPrintInput() print config.debugPrintSettings() wizardTune(config,options) outputFileName=options.outputConfig if outputFileName==None: outputFile=sys.stdout else: outputFile=open(outputFileName,'w') config.writeConfigFile(outputFile)