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 IOKIT_ENABLE_SHARED_PTR |
30 | |
31 | #define __STDC_LIMIT_MACROS // what are the C++ equivalents? |
32 | #include <stdint.h> |
33 | |
34 | #include <IOKit/IOKernelReportStructs.h> |
35 | #include <IOKit/IOKernelReporters.h> |
36 | #include <os/overflow.h> |
37 | #include "IOReporterDefs.h" |
38 | |
39 | |
40 | #define super IOReporter |
41 | OSDefineMetaClassAndStructors(IOHistogramReporter, IOReporter); |
42 | |
43 | /* static */ |
44 | OSSharedPtr<IOHistogramReporter> |
45 | IOHistogramReporter::with(IOService *reportingService, |
46 | IOReportCategories categories, |
47 | uint64_t channelID, |
48 | const char *channelName, |
49 | IOReportUnit unit, |
50 | int nSegments, |
51 | IOHistogramSegmentConfig *config) |
52 | { |
53 | OSSharedPtr<IOHistogramReporter> reporter = OSMakeShared<IOHistogramReporter>(); |
54 | OSSharedPtr<const OSSymbol> tmpChannelName; |
55 | |
56 | if (reporter) { |
57 | if (channelName) { |
58 | tmpChannelName = OSSymbol::withCString(cString: channelName); |
59 | } |
60 | |
61 | if (reporter->initWith(reportingService, categories, |
62 | channelID, channelName: tmpChannelName.get(), |
63 | unit, nSegments, config)) { |
64 | return reporter; |
65 | } |
66 | } |
67 | |
68 | return nullptr; |
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) { |
110 | _channelNames->setObject(channelName); |
111 | } |
112 | |
113 | _segmentCount = nSegments; |
114 | if (_segmentCount == 0) { |
115 | IORLOG("IOReportHistogram init ERROR. No configuration provided!" ); |
116 | result = false; |
117 | goto finish; |
118 | } |
119 | |
120 | IORLOG("%s - %u segment(s)" , __func__, _segmentCount); |
121 | |
122 | PREFL_MEMOP_FAIL(_segmentCount, IOHistogramSegmentConfig); |
123 | configSize = (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig); |
124 | _histogramSegmentsConfig = (IOHistogramSegmentConfig*)IOMallocData(configSize); |
125 | if (!_histogramSegmentsConfig) { |
126 | goto finish; |
127 | } |
128 | memcpy(dst: _histogramSegmentsConfig, src: config, n: configSize); |
129 | |
130 | // Find out how many elements are need to store the histogram |
131 | for (cnt = 0; cnt < _segmentCount; cnt++) { |
132 | _nElements += _histogramSegmentsConfig[cnt].segment_bucket_count; |
133 | _channelDimension += _histogramSegmentsConfig[cnt].segment_bucket_count; |
134 | |
135 | IORLOG("\t\t bucket_base_width: %u | log_scale: %u | buckets: %u" , |
136 | _histogramSegmentsConfig[cnt].base_bucket_width, |
137 | _histogramSegmentsConfig[cnt].scale_flag, |
138 | _histogramSegmentsConfig[cnt].segment_bucket_count); |
139 | |
140 | if (_histogramSegmentsConfig[cnt].scale_flag > 1 |
141 | || _histogramSegmentsConfig[cnt].base_bucket_width == 0) { |
142 | result = false; |
143 | goto finish; |
144 | } |
145 | } |
146 | |
147 | // Update the channel type with discovered dimension |
148 | _channelType.nelements = _channelDimension; |
149 | |
150 | IORLOG("%s - %u channel(s) of dimension %u" , |
151 | __func__, _nChannels, _channelDimension); |
152 | |
153 | IORLOG("%s %d segments for a total dimension of %d elements" , |
154 | __func__, _nChannels, _nElements); |
155 | |
156 | // Allocate memory for the array of report elements |
157 | PREFL_MEMOP_FAIL(_nElements, IOReportElement); |
158 | elementsSize = (size_t)_nElements * sizeof(IOReportElement); |
159 | _elements = (IOReportElement *)IOMallocZeroData(elementsSize); |
160 | if (!_elements) { |
161 | goto finish; |
162 | } |
163 | |
164 | // Allocate memory for the array of element watch count |
165 | PREFL_MEMOP_FAIL(_nElements, int); |
166 | eCountsSize = (size_t)_nChannels * sizeof(int); |
167 | _enableCounts = (int *)IOMallocZeroData(eCountsSize); |
168 | if (!_enableCounts) { |
169 | goto finish; |
170 | } |
171 | |
172 | lockReporter(); |
173 | for (cnt2 = 0; cnt2 < _channelDimension; cnt2++) { |
174 | IOHistogramReportValues hist_values; |
175 | if (copyElementValues(element_index: cnt2, elementValues: (IOReportElementValues*)&hist_values)) { |
176 | goto finish; |
177 | } |
178 | hist_values.bucket_min = kIOReportInvalidIntValue; |
179 | hist_values.bucket_max = kIOReportInvalidIntValue; |
180 | hist_values.bucket_sum = kIOReportInvalidIntValue; |
181 | if (setElementValues(element_index: cnt2, values: (IOReportElementValues*)&hist_values)) { |
182 | goto finish; |
183 | } |
184 | |
185 | // Setup IOReporter's channel IDs |
186 | _elements[cnt2].channel_id = channelID; |
187 | |
188 | // Setup IOReporter's reporting provider service |
189 | _elements[cnt2].provider_id = _driver_id; |
190 | |
191 | // Setup IOReporter's channel type |
192 | _elements[cnt2].channel_type = _channelType; |
193 | _elements[cnt2].channel_type.element_idx = ((int16_t) cnt2); |
194 | |
195 | //IOREPORTER_DEBUG_ELEMENT(cnt2); |
196 | } |
197 | unlockReporter(); |
198 | |
199 | // Allocate memory for the bucket upper bounds |
200 | PREFL_MEMOP_FAIL(_nElements, uint64_t); |
201 | boundsSize = (size_t)_nElements * sizeof(uint64_t); |
202 | _bucketBounds = (int64_t*)IOMallocZeroData(boundsSize); |
203 | if (!_bucketBounds) { |
204 | goto finish; |
205 | } |
206 | _bucketCount = _nElements; |
207 | |
208 | for (cnt = 0; cnt < _segmentCount; cnt++) { |
209 | if (_histogramSegmentsConfig[cnt].segment_bucket_count > INT_MAX |
210 | || _histogramSegmentsConfig[cnt].base_bucket_width > INT_MAX) { |
211 | goto finish; |
212 | } |
213 | for (cnt2 = 0; cnt2 < (int)_histogramSegmentsConfig[cnt].segment_bucket_count; cnt2++) { |
214 | if (cnt3 >= _nElements) { |
215 | IORLOG("ERROR: _bucketBounds init" ); |
216 | result = false; |
217 | goto finish; |
218 | } |
219 | |
220 | if (_histogramSegmentsConfig[cnt].scale_flag) { |
221 | int64_t power = 1; |
222 | int exponent = cnt2 + 1; |
223 | while (exponent) { |
224 | power *= _histogramSegmentsConfig[cnt].base_bucket_width; |
225 | exponent--; |
226 | } |
227 | bucketBound = power; |
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 | IOFreeData(address: _bucketBounds, size: (size_t)_nElements * sizeof(int64_t)); |
261 | } |
262 | if (_histogramSegmentsConfig) { |
263 | PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig); |
264 | IOFreeData(address: _histogramSegmentsConfig, |
265 | size: (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig)); |
266 | } |
267 | |
268 | super::free(); |
269 | } |
270 | |
271 | |
272 | OSSharedPtr<IOReportLegendEntry> |
273 | IOHistogramReporter::handleCreateLegend(void) |
274 | { |
275 | OSSharedPtr<IOReportLegendEntry> legendEntry; |
276 | OSSharedPtr<OSData> tmpConfigData; |
277 | OSDictionary *tmpDict; // no refcount |
278 | |
279 | legendEntry = super::handleCreateLegend(); |
280 | if (!legendEntry) { |
281 | return nullptr; |
282 | } |
283 | |
284 | PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig); |
285 | tmpConfigData = OSData::withBytes(bytes: _histogramSegmentsConfig, |
286 | numBytes: (unsigned)_segmentCount * |
287 | sizeof(IOHistogramSegmentConfig)); |
288 | if (!tmpConfigData) { |
289 | return nullptr; |
290 | } |
291 | |
292 | tmpDict = OSDynamicCast(OSDictionary, |
293 | legendEntry->getObject(kIOReportLegendInfoKey)); |
294 | if (!tmpDict) { |
295 | return nullptr; |
296 | } |
297 | |
298 | tmpDict->setObject(kIOReportLegendConfigKey, anObject: tmpConfigData.get()); |
299 | |
300 | return legendEntry; |
301 | } |
302 | |
303 | IOReturn |
304 | IOHistogramReporter::overrideBucketValues(unsigned int index, |
305 | uint64_t bucket_hits, |
306 | int64_t bucket_min, |
307 | int64_t bucket_max, |
308 | int64_t bucket_sum) |
309 | { |
310 | IOReturn result; |
311 | IOHistogramReportValues bucket; |
312 | lockReporter(); |
313 | |
314 | if (index >= (unsigned int)_bucketCount) { |
315 | result = kIOReturnBadArgument; |
316 | goto finish; |
317 | } |
318 | |
319 | bucket.bucket_hits = bucket_hits; |
320 | bucket.bucket_min = bucket_min; |
321 | bucket.bucket_max = bucket_max; |
322 | bucket.bucket_sum = bucket_sum; |
323 | |
324 | result = setElementValues(element_index: index, values: (IOReportElementValues *)&bucket); |
325 | finish: |
326 | unlockReporter(); |
327 | return result; |
328 | } |
329 | |
330 | int |
331 | IOHistogramReporter::tallyValue(int64_t value) |
332 | { |
333 | int result = -1; |
334 | int cnt = 0, element_index = 0; |
335 | int64_t sum = 0; |
336 | IOHistogramReportValues hist_values; |
337 | |
338 | lockReporter(); |
339 | |
340 | // Iterate over _bucketCount minus one to make last bucket of infinite width |
341 | for (cnt = 0; cnt < _bucketCount - 1; cnt++) { |
342 | if (value <= _bucketBounds[cnt]) { |
343 | break; |
344 | } |
345 | } |
346 | |
347 | element_index = cnt; |
348 | |
349 | if (copyElementValues(element_index, elementValues: (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 | if (os_add_overflow(hist_values.bucket_sum, value, &sum)) { |
366 | hist_values.bucket_sum = INT64_MAX; |
367 | } else { |
368 | hist_values.bucket_sum = sum; |
369 | } |
370 | hist_values.bucket_hits++; |
371 | |
372 | if (setElementValues(element_index, values: (IOReportElementValues *)&hist_values) |
373 | != kIOReturnSuccess) { |
374 | goto finish; |
375 | } |
376 | |
377 | // success! |
378 | result = element_index; |
379 | |
380 | finish: |
381 | unlockReporter(); |
382 | return result; |
383 | } |
384 | |
385 | /* static */ OSPtr<IOReportLegendEntry> |
386 | IOHistogramReporter::createLegend(uint64_t channelID, |
387 | const char *channelName, |
388 | int segmentCount, |
389 | IOHistogramSegmentConfig *config, |
390 | IOReportCategories categories, |
391 | IOReportUnit unit) |
392 | { |
393 | OSSharedPtr<IOReportLegendEntry> legendEntry; |
394 | OSSharedPtr<OSData> tmpConfigData; |
395 | OSDictionary *tmpDict; // no refcount |
396 | int cnt; |
397 | |
398 | IOReportChannelType channelType = { |
399 | .categories = categories, |
400 | .report_format = kIOReportFormatHistogram, |
401 | .nelements = 0, |
402 | .element_idx = 0 |
403 | }; |
404 | |
405 | for (cnt = 0; cnt < segmentCount; cnt++) { |
406 | channelType.nelements += config[cnt].segment_bucket_count; |
407 | } |
408 | |
409 | legendEntry = IOReporter::legendWith(channelIDs: &channelID, channelNames: &channelName, channelCount: 1, channelType, unit); |
410 | if (!legendEntry) { |
411 | return nullptr; |
412 | } |
413 | |
414 | PREFL_MEMOP_PANIC(segmentCount, IOHistogramSegmentConfig); |
415 | tmpConfigData = OSData::withBytes(bytes: config, |
416 | numBytes: (unsigned)segmentCount * |
417 | sizeof(IOHistogramSegmentConfig)); |
418 | if (!tmpConfigData) { |
419 | return nullptr; |
420 | } |
421 | |
422 | tmpDict = OSDynamicCast(OSDictionary, |
423 | legendEntry->getObject(kIOReportLegendInfoKey)); |
424 | if (!tmpDict) { |
425 | return nullptr; |
426 | } |
427 | |
428 | tmpDict->setObject(kIOReportLegendConfigKey, anObject: tmpConfigData.get()); |
429 | |
430 | return legendEntry; |
431 | } |
432 | |