/* * Copyright (c) 2012-2013, 2015, 2018-2019 ARM Limited * Copyright (c) 2016 Google Inc. * Copyright (c) 2017, Centre National de la Recherche Scientifique * All rights reserved. * * The license below extends only to copyright in the software and shall * not be construed as granting a license to any other intellectual * property including but not limited to intellectual property relating * to a hardware implementation of the functionality of the software * licensed hereunder. You may use the software subject to the license * terms below provided that you ensure that this notice is replicated * unmodified and in its entirety in all distributions of the software, * modified or unmodified, in source code or in binary form. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "mem/comm_monitor.hh" #include "base/trace.hh" #include "debug/CommMonitor.hh" #include "sim/stats.hh" CommMonitor::CommMonitor(const Params ¶ms) : SimObject(params), memSidePort(name() + "-mem_side_port", *this), cpuSidePort(name() + "-cpu_side_port", *this), samplePeriodicEvent([this]{ samplePeriodic(); }, name()), samplePeriodTicks(params.sample_period), samplePeriod(params.sample_period / sim_clock::as_float::s), stats(this, params) { DPRINTF(CommMonitor, "Created monitor %s with sample period %d ticks (%f ms)\n", name(), samplePeriodTicks, samplePeriod * 1E3); } void CommMonitor::init() { // make sure both sides of the monitor are connected if (!cpuSidePort.isConnected() || !memSidePort.isConnected()) fatal("Communication monitor is not connected on both sides.\n"); } void CommMonitor::regProbePoints() { ppPktReq.reset(new probing::Packet(getProbeManager(), "PktRequest")); ppPktResp.reset(new probing::Packet(getProbeManager(), "PktResponse")); } Port & CommMonitor::getPort(const std::string &if_name, PortID idx) { if (if_name == "mem_side_port") { return memSidePort; } else if (if_name == "cpu_side_port") { return cpuSidePort; } else { return SimObject::getPort(if_name, idx); } } void CommMonitor::recvFunctional(PacketPtr pkt) { memSidePort.sendFunctional(pkt); } void CommMonitor::recvFunctionalSnoop(PacketPtr pkt) { cpuSidePort.sendFunctionalSnoop(pkt); } CommMonitor::MonitorStats::MonitorStats(statistics::Group *parent, const CommMonitorParams ¶ms) : statistics::Group(parent), disableBurstLengthHists(params.disable_burst_length_hists), ADD_STAT(readBurstLengthHist, statistics::units::Byte::get(), "Histogram of burst lengths of transmitted packets"), ADD_STAT(writeBurstLengthHist, statistics::units::Byte::get(), "Histogram of burst lengths of transmitted packets"), disableBandwidthHists(params.disable_bandwidth_hists), readBytes(0), ADD_STAT(readBandwidthHist, statistics::units::Rate< statistics::units::Byte, statistics::units::Second>::get(), "Histogram of read bandwidth per sample period"), ADD_STAT(totalReadBytes, statistics::units::Byte::get(), "Number of bytes read"), ADD_STAT(averageReadBandwidth, statistics::units::Rate< statistics::units::Byte, statistics::units::Second>::get(), "Average read bandwidth", totalReadBytes / simSeconds), writtenBytes(0), ADD_STAT(writeBandwidthHist, statistics::units::Rate< statistics::units::Byte, statistics::units::Second>::get(), "Histogram of write bandwidth"), ADD_STAT(totalWrittenBytes, statistics::units::Rate< statistics::units::Byte, statistics::units::Second>::get(), "Number of bytes written"), ADD_STAT(averageWriteBandwidth, statistics::units::Rate< statistics::units::Byte, statistics::units::Second>::get(), "Average write bandwidth", totalWrittenBytes / simSeconds), disableLatencyHists(params.disable_latency_hists), ADD_STAT(readLatencyHist, statistics::units::Tick::get(), "Read request-response latency"), ADD_STAT(writeLatencyHist, statistics::units::Tick::get(), "Write request-response latency"), disableITTDists(params.disable_itt_dists), ADD_STAT(ittReadRead, statistics::units::Tick::get(), "Read-to-read inter transaction time"), ADD_STAT(ittWriteWrite, statistics::units::Tick::get(), "Write-to-write inter transaction time"), ADD_STAT(ittReqReq, statistics::units::Tick::get(), "Request-to-request inter transaction time"), timeOfLastRead(0), timeOfLastWrite(0), timeOfLastReq(0), disableOutstandingHists(params.disable_outstanding_hists), ADD_STAT(outstandingReadsHist, statistics::units::Count::get(), "Outstanding read transactions"), outstandingReadReqs(0), ADD_STAT(outstandingWritesHist, statistics::units::Count::get(), "Outstanding write transactions"), outstandingWriteReqs(0), disableTransactionHists(params.disable_transaction_hists), ADD_STAT(readTransHist, statistics::units::Count::get(), "Histogram of read transactions per sample period"), readTrans(0), ADD_STAT(writeTransHist, statistics::units::Count::get(), "Histogram of write transactions per sample period"), writeTrans(0), disableAddrDists(params.disable_addr_dists), readAddrMask(params.read_addr_mask), writeAddrMask(params.write_addr_mask), ADD_STAT(readAddrDist, statistics::units::Count::get(), "Read address distribution"), ADD_STAT(writeAddrDist, statistics::units::Count::get(), "Write address distribution") { using namespace statistics; readBurstLengthHist .init(params.burst_length_bins) .flags(disableBurstLengthHists ? nozero : pdf); writeBurstLengthHist .init(params.burst_length_bins) .flags(disableBurstLengthHists ? nozero : pdf); // Stats based on received responses readBandwidthHist .init(params.bandwidth_bins) .flags(disableBandwidthHists ? nozero : pdf); averageReadBandwidth .flags(disableBandwidthHists ? nozero : pdf); totalReadBytes .flags(disableBandwidthHists ? nozero : pdf); // Stats based on successfully sent requests writeBandwidthHist .init(params.bandwidth_bins) .flags(disableBandwidthHists ? (pdf | nozero) : pdf); averageWriteBandwidth .flags(disableBandwidthHists ? nozero : pdf); totalWrittenBytes .flags(disableBandwidthHists ? nozero : pdf); readLatencyHist .init(params.latency_bins) .flags(disableLatencyHists ? nozero : pdf); writeLatencyHist .init(params.latency_bins) .flags(disableLatencyHists ? nozero : pdf); ittReadRead .init(1, params.itt_max_bin, params.itt_max_bin / params.itt_bins) .flags(disableITTDists ? nozero : pdf); ittWriteWrite .init(1, params.itt_max_bin, params.itt_max_bin / params.itt_bins) .flags(disableITTDists ? nozero : pdf); ittReqReq .init(1, params.itt_max_bin, params.itt_max_bin / params.itt_bins) .flags(disableITTDists ? nozero : pdf); outstandingReadsHist .init(params.outstanding_bins) .flags(disableOutstandingHists ? nozero : pdf); outstandingWritesHist .init(params.outstanding_bins) .flags(disableOutstandingHists ? nozero : pdf); readTransHist .init(params.transaction_bins) .flags(disableTransactionHists ? nozero : pdf); writeTransHist .init(params.transaction_bins) .flags(disableTransactionHists ? nozero : pdf); readAddrDist .init(0) .flags(disableAddrDists ? nozero : pdf); writeAddrDist .init(0) .flags(disableAddrDists ? nozero : pdf); } void CommMonitor::MonitorStats::updateReqStats( const probing::PacketInfo& pkt_info, bool is_atomic, bool expects_response) { if (pkt_info.cmd.isRead()) { // Increment number of observed read transactions if (!disableTransactionHists) ++readTrans; // Get sample of burst length if (!disableBurstLengthHists) readBurstLengthHist.sample(pkt_info.size); // Sample the masked address if (!disableAddrDists) readAddrDist.sample(pkt_info.addr & readAddrMask); if (!disableITTDists) { // Sample value of read-read inter transaction time if (timeOfLastRead != 0) ittReadRead.sample(curTick() - timeOfLastRead); timeOfLastRead = curTick(); // Sample value of req-req inter transaction time if (timeOfLastReq != 0) ittReqReq.sample(curTick() - timeOfLastReq); timeOfLastReq = curTick(); } if (!is_atomic && !disableOutstandingHists && expects_response) ++outstandingReadReqs; } else if (pkt_info.cmd.isWrite()) { // Same as for reads if (!disableTransactionHists) ++writeTrans; if (!disableBurstLengthHists) writeBurstLengthHist.sample(pkt_info.size); // Update the bandwidth stats on the request if (!disableBandwidthHists) { writtenBytes += pkt_info.size; totalWrittenBytes += pkt_info.size; } // Sample the masked write address if (!disableAddrDists) writeAddrDist.sample(pkt_info.addr & writeAddrMask); if (!disableITTDists) { // Sample value of write-to-write inter transaction time if (timeOfLastWrite != 0) ittWriteWrite.sample(curTick() - timeOfLastWrite); timeOfLastWrite = curTick(); // Sample value of req-to-req inter transaction time if (timeOfLastReq != 0) ittReqReq.sample(curTick() - timeOfLastReq); timeOfLastReq = curTick(); } if (!is_atomic && !disableOutstandingHists && expects_response) ++outstandingWriteReqs; } } void CommMonitor::MonitorStats::updateRespStats( const probing::PacketInfo& pkt_info, Tick latency, bool is_atomic) { if (pkt_info.cmd.isRead()) { // Decrement number of outstanding read requests if (!is_atomic && !disableOutstandingHists) { assert(outstandingReadReqs != 0); --outstandingReadReqs; } if (!disableLatencyHists) readLatencyHist.sample(latency); // Update the bandwidth stats based on responses for reads if (!disableBandwidthHists) { readBytes += pkt_info.size; totalReadBytes += pkt_info.size; } } else if (pkt_info.cmd.isWrite()) { // Decrement number of outstanding write requests if (!is_atomic && !disableOutstandingHists) { assert(outstandingWriteReqs != 0); --outstandingWriteReqs; } if (!disableLatencyHists) writeLatencyHist.sample(latency); } } Tick CommMonitor::recvAtomic(PacketPtr pkt) { const bool expects_response(pkt->needsResponse() && !pkt->cacheResponding()); probing::PacketInfo req_pkt_info(pkt); ppPktReq->notify(req_pkt_info); const Tick delay(memSidePort.sendAtomic(pkt)); stats.updateReqStats(req_pkt_info, true, expects_response); if (expects_response) stats.updateRespStats(req_pkt_info, delay, true); // Some packets, such as WritebackDirty, don't need response. assert(pkt->isResponse() || !expects_response); probing::PacketInfo resp_pkt_info(pkt); ppPktResp->notify(resp_pkt_info); return delay; } Tick CommMonitor::recvAtomicSnoop(PacketPtr pkt) { return cpuSidePort.sendAtomicSnoop(pkt); } bool CommMonitor::recvTimingReq(PacketPtr pkt) { // should always see a request assert(pkt->isRequest()); // Store relevant fields of packet, because packet may be modified // or even deleted when sendTiming() is called. const probing::PacketInfo pkt_info(pkt); const bool expects_response(pkt->needsResponse() && !pkt->cacheResponding()); // If a cache miss is served by a cache, a monitor near the memory // would see a request which needs a response, but this response // would not come back from the memory. Therefore we additionally // have to check the cacheResponding flag if (expects_response && !stats.disableLatencyHists) { pkt->pushSenderState(new CommMonitorSenderState(curTick())); } // Attempt to send the packet bool successful = memSidePort.sendTimingReq(pkt); // If not successful, restore the sender state if (!successful && expects_response && !stats.disableLatencyHists) { delete pkt->popSenderState(); } if (successful) { ppPktReq->notify(pkt_info); } if (successful) { DPRINTF(CommMonitor, "Forwarded %s request\n", pkt->isRead() ? "read" : pkt->isWrite() ? "write" : "non read/write"); stats.updateReqStats(pkt_info, false, expects_response); } return successful; } bool CommMonitor::recvTimingResp(PacketPtr pkt) { // should always see responses assert(pkt->isResponse()); // Store relevant fields of packet, because packet may be modified // or even deleted when sendTiming() is called. const probing::PacketInfo pkt_info(pkt); Tick latency = 0; CommMonitorSenderState* received_state = dynamic_cast(pkt->senderState); if (!stats.disableLatencyHists) { // Restore initial sender state if (received_state == NULL) panic("Monitor got a response without monitor sender state\n"); // Restore the sate pkt->senderState = received_state->predecessor; } // Attempt to send the packet bool successful = cpuSidePort.sendTimingResp(pkt); if (!stats.disableLatencyHists) { // If packet successfully send, sample value of latency, // afterwards delete sender state, otherwise restore state if (successful) { latency = curTick() - received_state->transmitTime; DPRINTF(CommMonitor, "Latency: %d\n", latency); delete received_state; } else { // Don't delete anything and let the packet look like we // did not touch it pkt->senderState = received_state; } } if (successful) { ppPktResp->notify(pkt_info); DPRINTF(CommMonitor, "Received %s response\n", pkt->isRead() ? "read" : pkt->isWrite() ? "write" : "non read/write"); stats.updateRespStats(pkt_info, latency, false); } return successful; } void CommMonitor::recvTimingSnoopReq(PacketPtr pkt) { cpuSidePort.sendTimingSnoopReq(pkt); } bool CommMonitor::recvTimingSnoopResp(PacketPtr pkt) { return memSidePort.sendTimingSnoopResp(pkt); } void CommMonitor::recvRetrySnoopResp() { cpuSidePort.sendRetrySnoopResp(); } bool CommMonitor::isSnooping() const { // check if the connected request port is snooping return cpuSidePort.isSnooping(); } AddrRangeList CommMonitor::getAddrRanges() const { // get the address ranges of the connected CPU-side port return memSidePort.getAddrRanges(); } void CommMonitor::recvReqRetry() { cpuSidePort.sendRetryReq(); } void CommMonitor::recvRespRetry() { memSidePort.sendRetryResp(); } bool CommMonitor::tryTiming(PacketPtr pkt) { return memSidePort.tryTiming(pkt); } void CommMonitor::recvRangeChange() { cpuSidePort.sendRangeChange(); } void CommMonitor::samplePeriodic() { // the periodic stats update runs on the granularity of sample // periods, but in combination with this there may also be a // external resets and dumps of the stats (through schedStatEvent) // causing the stats themselves to capture less than a sample // period // only capture if we have not reset the stats during the last // sample period if (simTicks.value() >= samplePeriodTicks) { if (!stats.disableTransactionHists) { stats.readTransHist.sample(stats.readTrans); stats.writeTransHist.sample(stats.writeTrans); } if (!stats.disableBandwidthHists) { stats.readBandwidthHist.sample(stats.readBytes / samplePeriod); stats.writeBandwidthHist.sample(stats.writtenBytes / samplePeriod); } if (!stats.disableOutstandingHists) { stats.outstandingReadsHist.sample(stats.outstandingReadReqs); stats.outstandingWritesHist.sample(stats.outstandingWriteReqs); } } // reset the sampled values stats.readTrans = 0; stats.writeTrans = 0; stats.readBytes = 0; stats.writtenBytes = 0; schedule(samplePeriodicEvent, curTick() + samplePeriodTicks); } void CommMonitor::startup() { schedule(samplePeriodicEvent, curTick() + samplePeriodTicks); }