/// <summary> /// sets the size in Bytes for a flow /// </summary> /// <param name="flowID"></param> /// <param name="sizeBytes"></param> public void CacheSetFlowSize(uint flowID, UInt64 sizeBytes) { FlowSLA sla = null; UInt64 oldFlowCacheSize = 0; // XXXET: bit of overkill, might need locks per flow lock (cacheLock) { if (false == FlowStats.TryGetValue(flowID, out sla)) { sla = new FlowSLA(flowID); FlowStats[flowID] = sla; if (noisyOutput) { Console.WriteLine("CacheSetFlowSize flow {0} old NA new {1}", flowID, sizeBytes); } } else { if (noisyOutput) { Console.WriteLine("CacheSetFlowSize flow {0} old {1} new {2}", flowID, sla.FlowCacheSize, sizeBytes); } } oldFlowCacheSize = sla.FlowCacheSize; sla.FlowCacheSize = sizeBytes; //Reset the cache counters, since hit rate will be different now sla.CacheAccessesHits = 0; sla.CacheAccessesTotal = 0; // if cache is shkrinking, need to Evict Evict(flowID); } //Shrink the free file cache element size accordingly if (oldFlowCacheSize > sla.FlowCacheSize) { Debug.Assert(sla.FlowCacheSize % this.AlignedBlockSize == 0); Int64 blocksToFree = (Int64)((oldFlowCacheSize - sla.FlowCacheSize) / this.AlignedBlockSize); cleanUpFreeFileCacheElementList(blocksToFree); } }
/// <summary> /// Intercepts a write request, caches it, then sends it down if doing write-through, or returns success and writes it out later if doing write-back /// XXXET: shares a lot of functionality with cacheIRP. Probably should be 1 function /// </summary> /// <param name="irp"></param> /// <returns></returns> public PreWriteReturnCode CacheWriteIRP(IRP irp) { Dictionary <string, Dictionary <uint, FileCacheElement> > f = null; Dictionary <uint, FileCacheElement> b = null; FileCacheElement ce = null; FlowSLA sla = null; bool blockUpdated = false; bool anyBlockUpdated = false; ulong savedFileOffset = irp.FileOffset; uint savedDataLength = irp.DataLength; uint dataOffset = irp.DataOffset; ulong fileOffset = savedFileOffset; bool writeHit = false; bool canSatisfyRequest = false; Debug.Assert(irp.IoFlowHeader.MajorFunction == MajorFunction.IRP_MJ_WRITE); if ((int)irp.IoFlowHeader.ProcessID != CacheProcessID) //don't intercept traffic we generated { if (noisyOutput) { Console.WriteLine("CacheWriteIRP {0}: Attempting to lock cache on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } Monitor.Enter(cacheLock); if (noisyOutput) { Console.WriteLine("CacheWriteIRP {0}: Locked cache on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } //string tempFileNameChunking = irp.IoFlowRuntime.getDriveLetterFileName(irp.IoFlow.FileName); //save file name here once, so we don't do multiple calls to this // iterate over all blocks // it's a hit if all blocks are a hit, otherwise its a miss for the entire IRP do { uint blockid = (uint)(fileOffset / ALIGNED_BLOCK_SIZE); //Get the flow stats list if (!FlowStats.TryGetValue(irp.FlowId, out sla)) { //sla = new FlowSLA(); //FlowStats[irp.FlowId] = sla; Debug.Assert(0 == 1); //XXXIS let's only deal with explicitly declared flows right now } if (!Cache.TryGetValue(irp.FlowId, out f)) { Cache[irp.FlowId] = new Dictionary <string, Dictionary <uint, FileCacheElement> >(); f = Cache[irp.FlowId]; } if (!f.TryGetValue(irp.IoFlow.FileName, out b)) { f[irp.IoFlow.FileName] = new Dictionary <uint, FileCacheElement>(); b = f[irp.IoFlow.FileName]; } if (!b.TryGetValue(blockid, out ce)) // block is not currently cached { if (this.cacheWrites == CacheWriteBuffer.Cache) // only cache the block if write caching is turned on { //b[blockid] = new FileCacheElement(irp.IoFlow, irp.IoFlowRuntime.getDriveLetterFileName(irp.IoFlow.FileName), null, // fileOffset, dataOffset, ALIGNED_BLOCK_SIZE /* copying data only */); b[blockid] = getFileCacheElement(irp.IoFlow, irp.IoFlow.FileName, null, fileOffset, dataOffset, ALIGNED_BLOCK_SIZE /* copying data only */); ce = b[blockid]; //Might need to evict if we don't have enough space for the new entry Evict(irp.FlowId); ce.UpdateNodeList(sla.cacheEntries.AddLast(ce)); //Just create the cache entry cacheSizeUsedBytes += ALIGNED_BLOCK_SIZE; sla.FlowCacheSizeUsedBytes += ALIGNED_BLOCK_SIZE; } } // block is in the cache; only update if the block has data in flight (eg: a read), or if write caching is turned on if ((this.cacheWrites == CacheWriteBuffer.noCache && ce != null) || this.cacheWrites == CacheWriteBuffer.Cache) { { lock (ce.LockObj) { if (noisyOutput) { Console.WriteLine("CacheWriteIRP {0}: Locked ce on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } if (noisyOutput) { Console.WriteLine("CacheWriteIRP {0}: Caching write on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } ce.UpdateData(irp.GetDataReadOnly(), dataOffset, ALIGNED_BLOCK_SIZE /* copying data */); blockUpdated = true; //Move to the front of the LRU list Debug.Assert(ce.nodeInList != null); sla.cacheEntries.Remove(ce.nodeInList); sla.cacheEntries.AddLast(ce.nodeInList); //XXXIS: send all writes to ghost cache if (sla.FlowSLAHasGhostCache()) { sla.GhostCache.CacheReadReference(ce.fileName + Convert.ToString(blockid)); //Forward the reference to the ghost cache } } } } fileOffset += ALIGNED_BLOCK_SIZE; dataOffset += ALIGNED_BLOCK_SIZE; if (blockUpdated == true) { anyBlockUpdated = true; } } while (fileOffset < savedFileOffset + savedDataLength); sla.CacheAccessesTotal++; //update total number of ops that passed through this cache sla.FlowBytesAccessed += irp.DataLength; if (writeHit) { sla.CacheAccessesHits++; } Monitor.Exit(cacheLock); if (noisyOutput) { Console.WriteLine("CacheWriteIRP {0}: UnLocked cache on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } if (this.writePolicy == CacheWritePolicy.WriteThrough || anyBlockUpdated == true) //if write-through, or waiting for disk to reply on a read for some block we wrote { return(PreWriteReturnCode.FLT_PREOP_SUCCESS_WITH_CALLBACK); //send it down } else { Debug.Assert(0 == 1); //we shouldn't be in this case } } return(PreWriteReturnCode.FLT_PREOP_COMPLETE); //return complete (ignore traffic we generated) }
/// <summary> /// Caches a given IRP on the way down /// </summary> /// <param name="irp"></param> /// <returns></returns> public PreReadReturnCode CacheIRP(IRP irp) { // lookup if IRP is already cached Dictionary <string, Dictionary <uint, FileCacheElement> > f = null; Dictionary <uint, FileCacheElement> b = null; FileCacheElement ce = null; bool hit = false; bool anyMisses = false; FlowSLA sla = null; ulong savedFileOffset = irp.FileOffset; uint savedDataLength = irp.DataLength; uint dataOffset = irp.DataOffset; ulong fileOffset = savedFileOffset; bool canSatisfyRequest = false; Debug.Assert(irp.IoFlowHeader.MajorFunction == MajorFunction.IRP_MJ_READ); if (noisyOutput) { Console.WriteLine("CacheIRP {0}: Attempting to lock cache on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } Monitor.Enter(cacheLock); sla = FlowStats[irp.FlowId]; Debug.Assert(sla != null); //Only deal with explicitly declared flows //Do we have enough cache space available to satisfy this request? if (sla.FlowCacheSize > irp.DataLength) { canSatisfyRequest = true; } if (noisyOutput) { Console.WriteLine("CacheIRP {0}: Locked cache on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } if (canSatisfyRequest) { // iterate over all blocks // it's a hit if all blocks are a hit, otherwise its a miss for the entire IRP do { uint blockid = (uint)(fileOffset / ALIGNED_BLOCK_SIZE); hit = false; //block isn't a hit yet { if (Cache.TryGetValue(irp.FlowId, out f)) { if (f.TryGetValue(irp.IoFlow.FileName, out b)) { if (b.TryGetValue(blockid, out ce)) { // cache entry exists if (noisyOutput) { Console.WriteLine("CacheIRP {0}: Attempting to lock ce on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } lock (ce.LockObj) { if (noisyOutput) { Console.WriteLine("CacheIRP {0}: Locked ce on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } if (ce.Data != null) { // real hit ; cache entry has data being read sla = FlowStats[irp.FlowId]; Debug.Assert(sla != null); //We should always have a valid sla entry if we have a hit hit = true; byte[] irpData = irp.GetDataReadWrite(); Debug.Assert(ce.DataLength == ALIGNED_BLOCK_SIZE); Buffer.BlockCopy(ce.Data, 0, irpData, (int)dataOffset, (int)ALIGNED_BLOCK_SIZE); Debug.Assert(ce.nodeInList != null); Debug.Assert(ce.nodeInList != null); sla.cacheEntries.Remove(ce.nodeInList); //Assumes no duplicate ce's in the list, which should be true... //ce.UpdateNodeList(sla.cacheEntries.AddLast(ce)); sla.cacheEntries.AddLast(ce.nodeInList); if (sla.FlowSLAHasGhostCache()) { sla.GhostCache.CacheReadReference(ce.fileName + Convert.ToString(blockid)); //Forward the reference to the ghost cache } } else { // cache entry exists, BUT data is still in-flight from storage medium hit = false; } } if (noisyOutput) { Console.WriteLine("CacheIRP {0}: UnLocked ce on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } } } } if (!hit) { // evict first Evict(irp.FlowId); // then insert if (f == null) { Cache[irp.FlowId] = new Dictionary <string, Dictionary <uint, FileCacheElement> >(); f = Cache[irp.FlowId]; } if (b == null) { f[irp.IoFlow.FileName] = new Dictionary <uint, FileCacheElement>(); b = f[irp.IoFlow.FileName]; } if (ce == null) { //b[blockid] = new FileCacheElement(irp.IoFlow, irp.IoFlowRuntime.getDriveLetterFileName(irp.IoFlow.FileName), null, // fileOffset, dataOffset, ALIGNED_BLOCK_SIZE /* copying data only */); //string tempFileNameChunking = irp.IoFlowRuntime.getDriveLetterFileName(irp.IoFlow.FileName); //save file name here once, so we don't do multiple calls to this b[blockid] = getFileCacheElement(irp.IoFlow, irp.IoFlow.FileName, null, fileOffset, dataOffset, ALIGNED_BLOCK_SIZE /* copying data only */); ce = b[blockid]; // insert element into list if (false == FlowStats.TryGetValue(irp.FlowId, out sla)) { //sla = new FlowSLA(); //FlowStats[irp.FlowId] = sla; Debug.Assert(0 == 1); //XXXIS let's only deal with explicitly declared flows right now } ce.UpdateNodeList(sla.cacheEntries.AddLast(ce)); cacheSizeUsedBytes += ALIGNED_BLOCK_SIZE; sla.FlowCacheSizeUsedBytes += ALIGNED_BLOCK_SIZE; if (sla.FlowSLAHasGhostCache()) { sla.GhostCache.CacheReadReference(ce.fileName + Convert.ToString(blockid)); //Forward the reference to the ghost cache } } } } if (noisyOutput) { Console.WriteLine("CacheIRP {0}: UnLock cache on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, fileOffset, ALIGNED_BLOCK_SIZE); } fileOffset += ALIGNED_BLOCK_SIZE; dataOffset += ALIGNED_BLOCK_SIZE; if (hit == false) { anyMisses = true; } } while (fileOffset < savedFileOffset + savedDataLength); if (false == FlowStats.TryGetValue(irp.FlowId, out sla)) { Debug.Assert(0 == 1); //XXXIS let's only deal with explicitly declared flows right now } if (!anyMisses) { sla.CacheAccessesHits++; //Increment the number of hits in the cache } } sla.CacheAccessesTotal++; //increment all the accesses to this cache sla.FlowBytesAccessed += irp.DataLength; Monitor.Exit(cacheLock); // deal with MISSES // Let IRP go through and intercept POST operation (with payload) // if (anyMisses == true || !canSatisfyRequest) { //Console.WriteLine("MISS: {0}", irp.FileOffset); if (noisyOutput) { Console.WriteLine("CacheIRP {0}: PreRead MISS Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, irp.FileOffset, irp.DataLength); } return(PreReadReturnCode.FLT_PREOP_SUCCESS_WITH_CALLBACK); } else { //Console.WriteLine("HIT: {0}", irp.FileOffset); if (noisyOutput) { Console.WriteLine("CacheIRP {0}: PreRead HIT Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, irp.FileOffset, irp.DataLength); } return(PreReadReturnCode.FLT_PREOP_COMPLETE); } }
/// <summary> /// Evicts an entry from the cache. Caller must have locked cache /// </summary> /// <param name="flowid">flow id of IRP that caused eviction (we evict from same flow)</param> private void Evict(uint flowid) { Dictionary <string, Dictionary <uint, FileCacheElement> > f = null; FlowSLA sla = null; FileCacheElement ce = null; Dictionary <uint, FileCacheElement> b = null; FileCacheElement ce2 = null; uint blockid = 0; if (false == Cache.TryGetValue(flowid, out f)) { return; } if (false == FlowStats.TryGetValue(flowid, out sla)) { return; } int block_pos = 0; // check if I need to evict while (sla.FlowCacheSizeUsedBytes > sla.FlowCacheSize && sla.cacheEntries.Count > 0) { int inner = 0; foreach (FileCacheElement node in sla.cacheEntries) { ce = node; if (inner == block_pos) { break; } inner++; } { lock (ce.LockObj) { if (ce.Data != null) { // real hit if (noisyOutput) { Console.WriteLine("Evict {0}: Locked ce on Offset={1} Length={2}", Thread.CurrentThread.ManagedThreadId, ce.FileOffset, ce.DataLength); } Debug.Assert(ce.DataLength == ALIGNED_BLOCK_SIZE); // remove from lookup dictionary blockid = (uint)(ce.FileOffset / ce.DataLength); if (f.TryGetValue(ce.Flow.FileName, out b)) { if (b.TryGetValue(blockid, out ce2)) { b.Remove(blockid); if (b.TryGetValue(blockid, out ce2)) { Debug.Assert(0 == 1); } } else { Debug.Assert(0 == 1); } } else { Debug.Assert(0 == 1); } // remove from linked list Debug.Assert(ce.nodeInList != null); sla.cacheEntries.Remove(ce.nodeInList); returnFreeFileCacheElement(ce); block_pos = 0; cacheSizeUsedBytes -= ce.DataLength; Debug.Assert(cacheSizeUsedBytes >= 0); sla.FlowCacheSizeUsedBytes -= ce.DataLength; sla.CacheTotalEvictions++; //Console.WriteLine("EVICT: {0}", ce.FileOffset); Debug.Assert(sla.FlowCacheSizeUsedBytes >= 0); } else { block_pos++; } } } } }
/// <summary> /// sets the size in Bytes for a flow /// </summary> /// <param name="flowID"></param> /// <param name="sizeBytes"></param> public void CacheSetFlowSize(uint flowID, UInt64 sizeBytes) { FlowSLA sla = null; UInt64 oldFlowCacheSize= 0; // XXXET: bit of overkill, might need locks per flow lock (cacheLock) { if (false == FlowStats.TryGetValue(flowID, out sla)) { sla = new FlowSLA(flowID); FlowStats[flowID] = sla; if (noisyOutput) Console.WriteLine("CacheSetFlowSize flow {0} old NA new {1}", flowID, sizeBytes); } else { if (noisyOutput) Console.WriteLine("CacheSetFlowSize flow {0} old {1} new {2}", flowID, sla.FlowCacheSize, sizeBytes); } oldFlowCacheSize = sla.FlowCacheSize; sla.FlowCacheSize = sizeBytes; //Reset the cache counters, since hit rate will be different now sla.CacheAccessesHits = 0; sla.CacheAccessesTotal = 0; // if cache is shkrinking, need to Evict Evict(flowID); } //Shrink the free file cache element size accordingly if (oldFlowCacheSize > sla.FlowCacheSize) { Debug.Assert(sla.FlowCacheSize % this.AlignedBlockSize == 0); Int64 blocksToFree = (Int64)((oldFlowCacheSize - sla.FlowCacheSize) / this.AlignedBlockSize); cleanUpFreeFileCacheElementList(blocksToFree); } }