/* * Copyright (c) 2012-2013 Apple Computer, Inc. All Rights Reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #define IOKIT_ENABLE_SHARED_PTR #define __STDC_LIMIT_MACROS // what are the C++ equivalents? #include #include #include #include #include "IOReporterDefs.h" #define super IOReporter OSDefineMetaClassAndStructors(IOHistogramReporter, IOReporter); /* static */ OSSharedPtr IOHistogramReporter::with(IOService *reportingService, IOReportCategories categories, uint64_t channelID, const char *channelName, IOReportUnit unit, int nSegments, IOHistogramSegmentConfig *config) { OSSharedPtr reporter = OSMakeShared(); OSSharedPtr tmpChannelName; if (reporter) { if (channelName) { tmpChannelName = OSSymbol::withCString(channelName); } if (reporter->initWith(reportingService, categories, channelID, tmpChannelName.get(), unit, nSegments, config)) { return reporter; } } return nullptr; } bool IOHistogramReporter::initWith(IOService *reportingService, IOReportCategories categories, uint64_t channelID, const OSSymbol *channelName, IOReportUnit unit, int nSegments, IOHistogramSegmentConfig *config) { bool result = false; IOReturn res; // for PREFL_MEMOP size_t configSize, elementsSize, eCountsSize, boundsSize; int cnt, cnt2, cnt3 = 0; int64_t bucketBound = 0, previousBucketBound = 0; // analyzer appeasement configSize = elementsSize = eCountsSize = boundsSize = 0; IORLOG("IOHistogramReporter::initWith"); // For now, this reporter is currently limited to a single channel _nChannels = 1; IOReportChannelType channelType = { .categories = categories, .report_format = kIOReportFormatHistogram, .nelements = 0, // Initialized when Config is unpacked .element_idx = 0 }; if (super::init(reportingService, channelType, unit) != true) { IORLOG("%s - ERROR: super::init failed", __func__); result = false; goto finish; } // Make sure to call this after the commit init phase if (channelName) { _channelNames->setObject(channelName); } _segmentCount = nSegments; if (_segmentCount == 0) { IORLOG("IOReportHistogram init ERROR. No configuration provided!"); result = false; goto finish; } IORLOG("%s - %u segment(s)", __func__, _segmentCount); PREFL_MEMOP_FAIL(_segmentCount, IOHistogramSegmentConfig); configSize = (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig); _histogramSegmentsConfig = (IOHistogramSegmentConfig*)IOMallocData(configSize); if (!_histogramSegmentsConfig) { goto finish; } memcpy(_histogramSegmentsConfig, config, configSize); // Find out how many elements are need to store the histogram for (cnt = 0; cnt < _segmentCount; cnt++) { _nElements += _histogramSegmentsConfig[cnt].segment_bucket_count; _channelDimension += _histogramSegmentsConfig[cnt].segment_bucket_count; IORLOG("\t\t bucket_base_width: %u | log_scale: %u | buckets: %u", _histogramSegmentsConfig[cnt].base_bucket_width, _histogramSegmentsConfig[cnt].scale_flag, _histogramSegmentsConfig[cnt].segment_bucket_count); if (_histogramSegmentsConfig[cnt].scale_flag > 1 || _histogramSegmentsConfig[cnt].base_bucket_width == 0) { result = false; goto finish; } } // Update the channel type with discovered dimension _channelType.nelements = _channelDimension; IORLOG("%s - %u channel(s) of dimension %u", __func__, _nChannels, _channelDimension); IORLOG("%s %d segments for a total dimension of %d elements", __func__, _nChannels, _nElements); // Allocate memory for the array of report elements PREFL_MEMOP_FAIL(_nElements, IOReportElement); elementsSize = (size_t)_nElements * sizeof(IOReportElement); _elements = (IOReportElement *)IOMallocZeroData(elementsSize); if (!_elements) { goto finish; } // Allocate memory for the array of element watch count PREFL_MEMOP_FAIL(_nElements, int); eCountsSize = (size_t)_nChannels * sizeof(int); _enableCounts = (int *)IOMallocZeroData(eCountsSize); if (!_enableCounts) { goto finish; } lockReporter(); for (cnt2 = 0; cnt2 < _channelDimension; cnt2++) { IOHistogramReportValues hist_values; if (copyElementValues(cnt2, (IOReportElementValues*)&hist_values)) { goto finish; } hist_values.bucket_min = kIOReportInvalidIntValue; hist_values.bucket_max = kIOReportInvalidIntValue; hist_values.bucket_sum = kIOReportInvalidIntValue; if (setElementValues(cnt2, (IOReportElementValues*)&hist_values)) { goto finish; } // Setup IOReporter's channel IDs _elements[cnt2].channel_id = channelID; // Setup IOReporter's reporting provider service _elements[cnt2].provider_id = _driver_id; // Setup IOReporter's channel type _elements[cnt2].channel_type = _channelType; _elements[cnt2].channel_type.element_idx = ((int16_t) cnt2); //IOREPORTER_DEBUG_ELEMENT(cnt2); } unlockReporter(); // Allocate memory for the bucket upper bounds PREFL_MEMOP_FAIL(_nElements, uint64_t); boundsSize = (size_t)_nElements * sizeof(uint64_t); _bucketBounds = (int64_t*)IOMallocZeroData(boundsSize); if (!_bucketBounds) { goto finish; } _bucketCount = _nElements; for (cnt = 0; cnt < _segmentCount; cnt++) { if (_histogramSegmentsConfig[cnt].segment_bucket_count > INT_MAX || _histogramSegmentsConfig[cnt].base_bucket_width > INT_MAX) { goto finish; } for (cnt2 = 0; cnt2 < (int)_histogramSegmentsConfig[cnt].segment_bucket_count; cnt2++) { if (cnt3 >= _nElements) { IORLOG("ERROR: _bucketBounds init"); result = false; goto finish; } if (_histogramSegmentsConfig[cnt].scale_flag) { int64_t power = 1; int exponent = cnt2 + 1; while (exponent) { power *= _histogramSegmentsConfig[cnt].base_bucket_width; exponent--; } bucketBound = power; } else { bucketBound = _histogramSegmentsConfig[cnt].base_bucket_width * ((unsigned)cnt2 + 1); } if (previousBucketBound >= bucketBound) { IORLOG("Histogram ERROR: bucket bound does not increase linearly (segment %u / bucket # %u)", cnt, cnt2); result = false; goto finish; } _bucketBounds[cnt3] = bucketBound; // IORLOG("_bucketBounds[%u] = %llu", cnt3, bucketBound); previousBucketBound = _bucketBounds[cnt3]; cnt3++; } } // success result = true; finish: return result; } void IOHistogramReporter::free(void) { if (_bucketBounds) { PREFL_MEMOP_PANIC(_nElements, int64_t); IOFreeData(_bucketBounds, (size_t)_nElements * sizeof(int64_t)); } if (_histogramSegmentsConfig) { PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig); IOFreeData(_histogramSegmentsConfig, (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig)); } super::free(); } OSSharedPtr IOHistogramReporter::handleCreateLegend(void) { OSSharedPtr legendEntry; OSSharedPtr tmpConfigData; OSDictionary *tmpDict; // no refcount legendEntry = super::handleCreateLegend(); if (!legendEntry) { return nullptr; } PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig); tmpConfigData = OSData::withBytes(_histogramSegmentsConfig, (unsigned)_segmentCount * sizeof(IOHistogramSegmentConfig)); if (!tmpConfigData) { return nullptr; } tmpDict = OSDynamicCast(OSDictionary, legendEntry->getObject(kIOReportLegendInfoKey)); if (!tmpDict) { return nullptr; } tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData.get()); return legendEntry; } IOReturn IOHistogramReporter::overrideBucketValues(unsigned int index, uint64_t bucket_hits, int64_t bucket_min, int64_t bucket_max, int64_t bucket_sum) { IOReturn result; IOHistogramReportValues bucket; lockReporter(); if (index >= (unsigned int)_bucketCount) { result = kIOReturnBadArgument; goto finish; } bucket.bucket_hits = bucket_hits; bucket.bucket_min = bucket_min; bucket.bucket_max = bucket_max; bucket.bucket_sum = bucket_sum; result = setElementValues(index, (IOReportElementValues *)&bucket); finish: unlockReporter(); return result; } int IOHistogramReporter::tallyValue(int64_t value) { int result = -1; int cnt = 0, element_index = 0; int64_t sum = 0; IOHistogramReportValues hist_values; lockReporter(); // Iterate over _bucketCount minus one to make last bucket of infinite width for (cnt = 0; cnt < _bucketCount - 1; cnt++) { if (value <= _bucketBounds[cnt]) { break; } } element_index = cnt; if (copyElementValues(element_index, (IOReportElementValues *)&hist_values) != kIOReturnSuccess) { goto finish; } // init stats on first hit if (hist_values.bucket_hits == 0) { hist_values.bucket_min = hist_values.bucket_max = value; hist_values.bucket_sum = 0; // += is below } // update all values if (value < hist_values.bucket_min) { hist_values.bucket_min = value; } else if (value > hist_values.bucket_max) { hist_values.bucket_max = value; } if (os_add_overflow(hist_values.bucket_sum, value, &sum)) { hist_values.bucket_sum = INT64_MAX; } else { hist_values.bucket_sum = sum; } hist_values.bucket_hits++; if (setElementValues(element_index, (IOReportElementValues *)&hist_values) != kIOReturnSuccess) { goto finish; } // success! result = element_index; finish: unlockReporter(); return result; } /* static */ OSPtr IOHistogramReporter::createLegend(uint64_t channelID, const char *channelName, int segmentCount, IOHistogramSegmentConfig *config, IOReportCategories categories, IOReportUnit unit) { OSSharedPtr legendEntry; OSSharedPtr tmpConfigData; OSDictionary *tmpDict; // no refcount int cnt; IOReportChannelType channelType = { .categories = categories, .report_format = kIOReportFormatHistogram, .nelements = 0, .element_idx = 0 }; for (cnt = 0; cnt < segmentCount; cnt++) { channelType.nelements += config[cnt].segment_bucket_count; } legendEntry = IOReporter::legendWith(&channelID, &channelName, 1, channelType, unit); if (!legendEntry) { return nullptr; } PREFL_MEMOP_PANIC(segmentCount, IOHistogramSegmentConfig); tmpConfigData = OSData::withBytes(config, (unsigned)segmentCount * sizeof(IOHistogramSegmentConfig)); if (!tmpConfigData) { return nullptr; } tmpDict = OSDynamicCast(OSDictionary, legendEntry->getObject(kIOReportLegendInfoKey)); if (!tmpDict) { return nullptr; } tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData.get()); return legendEntry; }