diff options
Diffstat (limited to 'app/perfdata.cpp')
-rw-r--r-- | app/perfdata.cpp | 163 |
1 files changed, 162 insertions, 1 deletions
diff --git a/app/perfdata.cpp b/app/perfdata.cpp index 5b10eaf..27a2095 100644 --- a/app/perfdata.cpp +++ b/app/perfdata.cpp @@ -33,11 +33,79 @@ PerfData::PerfData(PerfUnwind *destination, const PerfHeader *header, PerfAttrib { } +PerfData::~PerfData() +{ +#ifdef HAVE_ZSTD + if (m_zstdDstream) + ZSTD_freeDStream(m_zstdDstream); +#endif +} + void PerfData::setSource(QIODevice *source) { m_source = source; } +bool PerfData::setCompressed(const PerfCompressed &compressed) +{ + if (compressed.version != 0) { + qWarning() << "unsupported compression version" << compressed.version; + return false; + } else if (compressed.type != PerfCompressed::PERF_COMP_ZSTD) { + qWarning() << "unsupported compression type" << compressed.type; + return false; + } else if (!CAN_DECOMPRESS_ZSTD) { + qWarning() << "zstd decompression support not available"; + return false; + } else if (!compressed.mmap_len) { + qWarning() << "invalid compression information" << compressed.mmap_len; + return false; + } + m_compressed = compressed; + return true; +} + +const char * perfEventToString(qint32 type) +{ + switch (type) { + case PERF_RECORD_MMAP: return "PERF_RECORD_MMAP"; + case PERF_RECORD_LOST: return "PERF_RECORD_LOST"; + case PERF_RECORD_COMM: return "PERF_RECORD_COMM"; + case PERF_RECORD_EXIT: return "PERF_RECORD_EXIT"; + case PERF_RECORD_THROTTLE: return "PERF_RECORD_THROTTLE"; + case PERF_RECORD_UNTHROTTLE: return "PERF_RECORD_UNTHROTTLE"; + case PERF_RECORD_FORK: return "PERF_RECORD_FORK"; + case PERF_RECORD_READ: return "PERF_RECORD_READ"; + case PERF_RECORD_SAMPLE: return "PERF_RECORD_SAMPLE"; + case PERF_RECORD_MMAP2: return "PERF_RECORD_MMAP2"; + case PERF_RECORD_SWITCH: return "PERF_RECORD_SWITCH"; + case PERF_RECORD_SWITCH_CPU_WIDE: return "PERF_RECORD_SWITCH_CPU_WIDE"; + case PERF_RECORD_NAMESPACES: return "PERF_RECORD_NAMESPACES"; + case PERF_RECORD_KSYMBOL: return "PERF_RECORD_KSYMBOL"; + case PERF_RECORD_BPF_EVENT: return "PERF_RECORD_BPF_EVENT"; + case PERF_RECORD_CGROUP: return "PERF_RECORD_CGROUP"; + case PERF_RECORD_HEADER_ATTR: return "PERF_RECORD_HEADER_ATTR"; + case PERF_RECORD_HEADER_EVENT_TYPE: return "PERF_RECORD_HEADER_EVENT_TYPE"; + case PERF_RECORD_HEADER_TRACING_DATA: return "PERF_RECORD_HEADER_TRACING_DATA"; + case PERF_RECORD_HEADER_BUILD_ID: return "PERF_RECORD_HEADER_BUILD_ID"; + case PERF_RECORD_FINISHED_ROUND: return "PERF_RECORD_FINISHED_ROUND"; + case PERF_RECORD_ID_INDEX: return "PERF_RECORD_ID_INDEX"; + case PERF_RECORD_AUXTRACE_INFO: return "PERF_RECORD_AUXTRACE_INFO"; + case PERF_RECORD_AUXTRACE: return "PERF_RECORD_AUXTRACE"; + case PERF_RECORD_AUXTRACE_ERROR: return "PERF_RECORD_AUXTRACE_ERROR"; + case PERF_RECORD_THREAD_MAP: return "PERF_RECORD_THREAD_MAP"; + case PERF_RECORD_CPU_MAP: return "PERF_RECORD_CPU_MAP"; + case PERF_RECORD_STAT_CONFIG: return "PERF_RECORD_STAT_CONFIG"; + case PERF_RECORD_STAT: return "PERF_RECORD_STAT"; + case PERF_RECORD_STAT_ROUND: return "PERF_RECORD_STAT_ROUND"; + case PERF_RECORD_EVENT_UPDATE: return "PERF_RECORD_EVENT_UPDATE"; + case PERF_RECORD_TIME_CONV: return "PERF_RECORD_TIME_CONV"; + case PERF_RECORD_HEADER_FEATURE: return "PERF_RECORD_HEADER_FEATURE"; + case PERF_RECORD_COMPRESSED: return "PERF_RECORD_COMPRESSED"; + } + return "uknown type"; +} + PerfData::ReadStatus PerfData::processEvents(QDataStream &stream) { const quint16 headerSize = PerfEventHeader::fixedLength(); @@ -185,9 +253,92 @@ PerfData::ReadStatus PerfData::processEvents(QDataStream &stream) m_destination->contextSwitch(switchEvent); break; } + case PERF_RECORD_SWITCH_CPU_WIDE: { + PerfRecordContextSwitchCpuWide switchEvent(&m_eventHeader, sampleType, sampleIdAll); + stream >> switchEvent; + // TODO: also send prevNext{T,P}id, that would allow switch markers in the GUI to + // show where a switch comes from/goes to + m_destination->contextSwitch(switchEvent); + break; + } + +#ifdef HAVE_ZSTD + case PERF_RECORD_COMPRESSED: { + if (!m_zstdDstream) { + if (!m_header->hasFeature(PerfHeader::COMPRESSED)) { + qWarning() << "encountered PERF_RECORD_COMPRESSED without HEADER_COMPRESSED information"; + return SignalError; + } + + m_zstdDstream = ZSTD_createDStream(); + ZSTD_initDStream(m_zstdDstream); + + // preallocate a buffer to hold the compressed data + m_compressedBuffer.resize(std::numeric_limits<quint16>::max()); + } + + // load compressed data into contiguous array + stream.readRawData(m_compressedBuffer.data(), contentSize); + ZSTD_inBuffer in = {m_compressedBuffer.constData(), static_cast<size_t>(contentSize), 0}; + + // setup decompression buffer which may contain data from a previous compressed record + // i.e. one where we had to Rerun. the decompression can add at most mmap_len data on top + m_decompressBuffer.resize(m_compressed.mmap_len + m_remaininingDecompressedDataSize); + auto outBuffer = m_decompressBuffer.data() + m_remaininingDecompressedDataSize; + auto outBufferSize = static_cast<size_t>(m_decompressBuffer.size() - m_remaininingDecompressedDataSize); + ZSTD_outBuffer out = {outBuffer, outBufferSize, 0}; + + // now actually decompress the record data + while (in.pos < in.size) { + const auto err = ZSTD_decompressStream(m_zstdDstream, &out, &in); + if (ZSTD_isError(err)) { + qWarning() << "ZSTD decompression failed:" << ZSTD_getErrorName(err); + return SignalError; + } + out.dst = outBuffer + out.pos; + out.size = outBufferSize - out.pos; + } + + // then resize the buffer to final size, which may be less than mmap_len + m_decompressBuffer.resize(out.pos + m_remaininingDecompressedDataSize); + // reset this now that we start to parse from the start of the buffer again + m_remaininingDecompressedDataSize = 0; + + QDataStream uncompressedStream(m_decompressBuffer); + uncompressedStream.setByteOrder(m_header->byteOrder()); + // we have to set the size to zero here otherwise processEvents gets confused + m_eventHeader.size = 0; + auto status = SignalFinished; + while (status == SignalFinished) { + // position in the decompressed buffer that corresponds to a start of the next record + // when we encounter a Rerun scenario, we have to start again at this position the next time + const auto oldPos = uncompressedStream.device()->pos(); + status = processEvents(uncompressedStream); + switch (status) { + case SignalFinished: + break; + case SignalError: + return SignalError; + case Rerun: + // unset the device to prevent the m_decompressBuffer from being shared + // we don't want to copy the data when we call .begin() below + uncompressedStream.setDevice(nullptr); + // remaining decompressed data that needs to be parsed the next time + // we handle an uncompressed record + m_remaininingDecompressedDataSize = m_decompressBuffer.size() - oldPos; + // move that data up front in the buffer and continue appending data + std::move(m_decompressBuffer.begin() + oldPos, m_decompressBuffer.end(), + m_decompressBuffer.begin()); + break; + } + }; + + break; + } +#endif default: - qWarning() << "unhandled event type" << m_eventHeader.type; + qWarning() << "unhandled event type" << m_eventHeader.type << " " << perfEventToString(m_eventHeader.type); stream.skipRawData(contentSize); break; } @@ -658,3 +809,13 @@ QDataStream &operator>>(QDataStream &stream, PerfRecordContextSwitch &record) { return stream >> record.m_sampleId; } + +PerfRecordContextSwitchCpuWide::PerfRecordContextSwitchCpuWide(PerfEventHeader *header, quint64 sampleType, bool sampleIdAll) : + PerfRecordContextSwitch(header, sampleType, sampleIdAll) +{ +} + +QDataStream &operator>>(QDataStream &stream, PerfRecordContextSwitchCpuWide &record) +{ + return stream >> record.m_nextPrevPid >> record.m_nextPrevTid >> record.m_sampleId; +} |