| 1 | /* |
| 2 | * Copyright (c) 2012-2013 Apple Computer, Inc. All Rights Reserved. |
| 3 | * |
| 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
| 5 | * |
| 6 | * This file contains Original Code and/or Modifications of Original Code |
| 7 | * as defined in and that are subject to the Apple Public Source License |
| 8 | * Version 2.0 (the 'License'). You may not use this file except in |
| 9 | * compliance with the License. The rights granted to you under the License |
| 10 | * may not be used to create, or enable the creation or redistribution of, |
| 11 | * unlawful or unlicensed copies of an Apple operating system, or to |
| 12 | * circumvent, violate, or enable the circumvention or violation of, any |
| 13 | * terms of an Apple operating system software license agreement. |
| 14 | * |
| 15 | * Please obtain a copy of the License at |
| 16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. |
| 17 | * |
| 18 | * The Original Code and all software distributed under the License are |
| 19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
| 20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
| 21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
| 22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
| 23 | * Please see the License for the specific language governing rights and |
| 24 | * limitations under the License. |
| 25 | * |
| 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
| 27 | */ |
| 28 | |
| 29 | #define __STDC_LIMIT_MACROS // what are the C++ equivalents? |
| 30 | #include <stdint.h> |
| 31 | |
| 32 | #include <IOKit/IOKernelReportStructs.h> |
| 33 | #include <IOKit/IOKernelReporters.h> |
| 34 | #include "IOReporterDefs.h" |
| 35 | |
| 36 | |
| 37 | #define super IOReporter |
| 38 | OSDefineMetaClassAndStructors(IOHistogramReporter, IOReporter); |
| 39 | |
| 40 | /* static */ |
| 41 | IOHistogramReporter* |
| 42 | IOHistogramReporter::with(IOService *reportingService, |
| 43 | IOReportCategories categories, |
| 44 | uint64_t channelID, |
| 45 | const char *channelName, |
| 46 | IOReportUnit unit, |
| 47 | int nSegments, |
| 48 | IOHistogramSegmentConfig *config) |
| 49 | { |
| 50 | IOHistogramReporter *reporter = new IOHistogramReporter; |
| 51 | |
| 52 | const OSSymbol *tmpChannelName = NULL; |
| 53 | |
| 54 | if (reporter) { |
| 55 | |
| 56 | if (channelName) |
| 57 | tmpChannelName = OSSymbol::withCString(channelName); |
| 58 | |
| 59 | if(reporter->initWith(reportingService, categories, |
| 60 | channelID, tmpChannelName, |
| 61 | unit, nSegments, config)) { |
| 62 | return reporter; |
| 63 | } |
| 64 | } |
| 65 | OSSafeReleaseNULL(reporter); |
| 66 | OSSafeReleaseNULL(tmpChannelName); |
| 67 | |
| 68 | return 0; |
| 69 | } |
| 70 | |
| 71 | |
| 72 | bool |
| 73 | IOHistogramReporter::initWith(IOService *reportingService, |
| 74 | IOReportCategories categories, |
| 75 | uint64_t channelID, |
| 76 | const OSSymbol *channelName, |
| 77 | IOReportUnit unit, |
| 78 | int nSegments, |
| 79 | IOHistogramSegmentConfig *config) |
| 80 | { |
| 81 | bool result = false; |
| 82 | IOReturn res; // for PREFL_MEMOP |
| 83 | size_t configSize, elementsSize, eCountsSize, boundsSize; |
| 84 | int cnt, cnt2, cnt3 = 0; |
| 85 | int64_t bucketBound = 0, previousBucketBound = 0; |
| 86 | |
| 87 | // analyzer appeasement |
| 88 | configSize = elementsSize = eCountsSize = boundsSize = 0; |
| 89 | |
| 90 | IORLOG("IOHistogramReporter::initWith"); |
| 91 | |
| 92 | // For now, this reporter is currently limited to a single channel |
| 93 | _nChannels = 1; |
| 94 | |
| 95 | IOReportChannelType channelType = { |
| 96 | .categories = categories, |
| 97 | .report_format = kIOReportFormatHistogram, |
| 98 | .nelements = 0, // Initialized when Config is unpacked |
| 99 | .element_idx = 0 |
| 100 | }; |
| 101 | |
| 102 | if (super::init(reportingService, channelType, unit) != true) { |
| 103 | IORLOG("%s - ERROR: super::init failed", __func__); |
| 104 | result = false; |
| 105 | goto finish; |
| 106 | } |
| 107 | |
| 108 | // Make sure to call this after the commit init phase |
| 109 | if (channelName) _channelNames->setObject(channelName); |
| 110 | |
| 111 | _segmentCount = nSegments; |
| 112 | if (_segmentCount == 0) { |
| 113 | IORLOG("IOReportHistogram init ERROR. No configuration provided!"); |
| 114 | result = false; |
| 115 | goto finish; |
| 116 | } |
| 117 | |
| 118 | IORLOG("%s - %u segment(s)", __func__, _segmentCount); |
| 119 | |
| 120 | PREFL_MEMOP_FAIL(_segmentCount, IOHistogramSegmentConfig); |
| 121 | configSize = (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig); |
| 122 | _histogramSegmentsConfig = (IOHistogramSegmentConfig*)IOMalloc(configSize); |
| 123 | if (!_histogramSegmentsConfig) goto finish; |
| 124 | memcpy(_histogramSegmentsConfig, config, configSize); |
| 125 | |
| 126 | // Find out how many elements are need to store the histogram |
| 127 | for (cnt = 0; cnt < _segmentCount; cnt++) { |
| 128 | |
| 129 | _nElements += _histogramSegmentsConfig[cnt].segment_bucket_count; |
| 130 | _channelDimension += _histogramSegmentsConfig[cnt].segment_bucket_count; |
| 131 | |
| 132 | IORLOG("\t\t bucket_base_width: %u | log_scale: %u | buckets: %u", |
| 133 | _histogramSegmentsConfig[cnt].base_bucket_width, |
| 134 | _histogramSegmentsConfig[cnt].scale_flag, |
| 135 | _histogramSegmentsConfig[cnt].segment_bucket_count); |
| 136 | |
| 137 | if (_histogramSegmentsConfig[cnt].scale_flag > 1 |
| 138 | || _histogramSegmentsConfig[cnt].base_bucket_width == 0) { |
| 139 | result = false; |
| 140 | goto finish; |
| 141 | } |
| 142 | |
| 143 | } |
| 144 | |
| 145 | // Update the channel type with discovered dimension |
| 146 | _channelType.nelements = _channelDimension; |
| 147 | |
| 148 | IORLOG("%s - %u channel(s) of dimension %u", |
| 149 | __func__, _nChannels, _channelDimension); |
| 150 | |
| 151 | IORLOG("%s %d segments for a total dimension of %d elements", |
| 152 | __func__, _nChannels, _nElements); |
| 153 | |
| 154 | // Allocate memory for the array of report elements |
| 155 | PREFL_MEMOP_FAIL(_nElements, IOReportElement); |
| 156 | elementsSize = (size_t)_nElements * sizeof(IOReportElement); |
| 157 | _elements = (IOReportElement *)IOMalloc(elementsSize); |
| 158 | if (!_elements) goto finish; |
| 159 | memset(_elements, 0, elementsSize); |
| 160 | |
| 161 | // Allocate memory for the array of element watch count |
| 162 | PREFL_MEMOP_FAIL(_nElements, int); |
| 163 | eCountsSize = (size_t)_nChannels * sizeof(int); |
| 164 | _enableCounts = (int *)IOMalloc(eCountsSize); |
| 165 | if (!_enableCounts) goto finish; |
| 166 | memset(_enableCounts, 0, eCountsSize); |
| 167 | |
| 168 | lockReporter(); |
| 169 | for (cnt2 = 0; cnt2 < _channelDimension; cnt2++) { |
| 170 | IOHistogramReportValues hist_values; |
| 171 | if (copyElementValues(cnt2, (IOReportElementValues*)&hist_values)){ |
| 172 | goto finish; |
| 173 | } |
| 174 | hist_values.bucket_min = kIOReportInvalidIntValue; |
| 175 | hist_values.bucket_max = kIOReportInvalidIntValue; |
| 176 | hist_values.bucket_sum = kIOReportInvalidIntValue; |
| 177 | if (setElementValues(cnt2, (IOReportElementValues*)&hist_values)){ |
| 178 | goto finish; |
| 179 | } |
| 180 | |
| 181 | // Setup IOReporter's channel IDs |
| 182 | _elements[cnt2].channel_id = channelID; |
| 183 | |
| 184 | // Setup IOReporter's reporting provider service |
| 185 | _elements[cnt2].provider_id = _driver_id; |
| 186 | |
| 187 | // Setup IOReporter's channel type |
| 188 | _elements[cnt2].channel_type = _channelType; |
| 189 | _elements[cnt2].channel_type.element_idx = cnt2; |
| 190 | |
| 191 | //IOREPORTER_DEBUG_ELEMENT(cnt2); |
| 192 | } |
| 193 | unlockReporter(); |
| 194 | |
| 195 | // Allocate memory for the bucket upper bounds |
| 196 | PREFL_MEMOP_FAIL(_nElements, uint64_t); |
| 197 | boundsSize = (size_t)_nElements * sizeof(uint64_t); |
| 198 | _bucketBounds = (int64_t*)IOMalloc(boundsSize); |
| 199 | if (!_bucketBounds) goto finish; |
| 200 | memset(_bucketBounds, 0, boundsSize); |
| 201 | _bucketCount = _nElements; |
| 202 | |
| 203 | for (cnt = 0; cnt < _segmentCount; cnt++) { |
| 204 | |
| 205 | if (_histogramSegmentsConfig[cnt].segment_bucket_count > INT_MAX |
| 206 | || _histogramSegmentsConfig[cnt].base_bucket_width > INT_MAX) { |
| 207 | goto finish; |
| 208 | } |
| 209 | for (cnt2 = 0; cnt2 < (int)_histogramSegmentsConfig[cnt].segment_bucket_count; cnt2++) { |
| 210 | |
| 211 | if (cnt3 >= _nElements) { |
| 212 | IORLOG("ERROR: _bucketBounds init"); |
| 213 | result = false; |
| 214 | goto finish; |
| 215 | } |
| 216 | |
| 217 | if (_histogramSegmentsConfig[cnt].scale_flag) { |
| 218 | // FIXME: Could use pow() but not sure how to include math.h |
| 219 | int64_t power = 1; |
| 220 | int exponent = cnt2 + 1; |
| 221 | while (exponent) { |
| 222 | power *= _histogramSegmentsConfig[cnt].base_bucket_width; |
| 223 | exponent--; |
| 224 | } |
| 225 | bucketBound = power; |
| 226 | } |
| 227 | |
| 228 | else { |
| 229 | bucketBound = _histogramSegmentsConfig[cnt].base_bucket_width * |
| 230 | ((unsigned)cnt2 + 1); |
| 231 | } |
| 232 | |
| 233 | if (previousBucketBound >= bucketBound) { |
| 234 | IORLOG("Histogram ERROR: bucket bound does not increase linearly (segment %u / bucket # %u)", |
| 235 | cnt, cnt2); |
| 236 | result = false; |
| 237 | goto finish; |
| 238 | } |
| 239 | |
| 240 | _bucketBounds[cnt3] = bucketBound; |
| 241 | // IORLOG("_bucketBounds[%u] = %llu", cnt3, bucketBound); |
| 242 | previousBucketBound = _bucketBounds[cnt3]; |
| 243 | cnt3++; |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | // success |
| 248 | result = true; |
| 249 | |
| 250 | finish: |
| 251 | return result; |
| 252 | } |
| 253 | |
| 254 | |
| 255 | void |
| 256 | IOHistogramReporter::free(void) |
| 257 | { |
| 258 | if (_bucketBounds) { |
| 259 | PREFL_MEMOP_PANIC(_nElements, int64_t); |
| 260 | IOFree(_bucketBounds, (size_t)_nElements * sizeof(int64_t)); |
| 261 | } |
| 262 | if (_histogramSegmentsConfig) { |
| 263 | PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig); |
| 264 | IOFree(_histogramSegmentsConfig, |
| 265 | (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig)); |
| 266 | } |
| 267 | |
| 268 | super::free(); |
| 269 | } |
| 270 | |
| 271 | |
| 272 | IOReportLegendEntry* |
| 273 | IOHistogramReporter::handleCreateLegend(void) |
| 274 | { |
| 275 | IOReportLegendEntry *rval = NULL, *legendEntry = NULL; |
| 276 | OSData *tmpConfigData = NULL; |
| 277 | OSDictionary *tmpDict; // no refcount |
| 278 | |
| 279 | legendEntry = super::handleCreateLegend(); |
| 280 | if (!legendEntry) goto finish; |
| 281 | |
| 282 | PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig); |
| 283 | tmpConfigData = OSData::withBytes(_histogramSegmentsConfig, |
| 284 | (unsigned)_segmentCount * |
| 285 | sizeof(IOHistogramSegmentConfig)); |
| 286 | if (!tmpConfigData) goto finish; |
| 287 | |
| 288 | tmpDict = OSDynamicCast(OSDictionary, |
| 289 | legendEntry->getObject(kIOReportLegendInfoKey)); |
| 290 | if (!tmpDict) goto finish; |
| 291 | |
| 292 | tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData); |
| 293 | |
| 294 | // success |
| 295 | rval = legendEntry; |
| 296 | |
| 297 | finish: |
| 298 | if (tmpConfigData) tmpConfigData->release(); |
| 299 | if (!rval && legendEntry) { |
| 300 | legendEntry->release(); |
| 301 | } |
| 302 | |
| 303 | return rval; |
| 304 | } |
| 305 | |
| 306 | IOReturn |
| 307 | IOHistogramReporter::overrideBucketValues(unsigned int index, |
| 308 | uint64_t bucket_hits, |
| 309 | int64_t bucket_min, |
| 310 | int64_t bucket_max, |
| 311 | int64_t bucket_sum) |
| 312 | { |
| 313 | IOReturn result; |
| 314 | IOHistogramReportValues bucket; |
| 315 | lockReporter(); |
| 316 | |
| 317 | if (index >= (unsigned int)_bucketCount) { |
| 318 | result = kIOReturnBadArgument; |
| 319 | goto finish; |
| 320 | } |
| 321 | |
| 322 | bucket.bucket_hits = bucket_hits; |
| 323 | bucket.bucket_min = bucket_min; |
| 324 | bucket.bucket_max = bucket_max; |
| 325 | bucket.bucket_sum = bucket_sum; |
| 326 | |
| 327 | result = setElementValues(index, (IOReportElementValues *)&bucket); |
| 328 | finish: |
| 329 | unlockReporter(); |
| 330 | return result; |
| 331 | } |
| 332 | |
| 333 | int |
| 334 | IOHistogramReporter::tallyValue(int64_t value) |
| 335 | { |
| 336 | int result = -1; |
| 337 | int cnt = 0, element_index = 0; |
| 338 | IOHistogramReportValues hist_values; |
| 339 | |
| 340 | lockReporter(); |
| 341 | |
| 342 | // Iterate over _bucketCount minus one to make last bucket of infinite width |
| 343 | for (cnt = 0; cnt < _bucketCount - 1; cnt++) { |
| 344 | if (value <= _bucketBounds[cnt]) break; |
| 345 | } |
| 346 | |
| 347 | element_index = cnt; |
| 348 | |
| 349 | if (copyElementValues(element_index, (IOReportElementValues *)&hist_values) != kIOReturnSuccess) { |
| 350 | goto finish; |
| 351 | } |
| 352 | |
| 353 | // init stats on first hit |
| 354 | if (hist_values.bucket_hits == 0) { |
| 355 | hist_values.bucket_min = hist_values.bucket_max = value; |
| 356 | hist_values.bucket_sum = 0; // += is below |
| 357 | } |
| 358 | |
| 359 | // update all values |
| 360 | if (value < hist_values.bucket_min) { |
| 361 | hist_values.bucket_min = value; |
| 362 | } else if (value > hist_values.bucket_max) { |
| 363 | hist_values.bucket_max = value; |
| 364 | } |
| 365 | hist_values.bucket_sum += value; |
| 366 | hist_values.bucket_hits++; |
| 367 | |
| 368 | if (setElementValues(element_index, (IOReportElementValues *)&hist_values) |
| 369 | != kIOReturnSuccess) { |
| 370 | goto finish; |
| 371 | } |
| 372 | |
| 373 | // success! |
| 374 | result = element_index; |
| 375 | |
| 376 | finish: |
| 377 | unlockReporter(); |
| 378 | return result; |
| 379 | } |