/src/mozilla-central/netwerk/cache2/CacheFileUtils.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | #include "CacheIndex.h" |
6 | | #include "CacheLog.h" |
7 | | #include "CacheFileUtils.h" |
8 | | #include "LoadContextInfo.h" |
9 | | #include "mozilla/Tokenizer.h" |
10 | | #include "mozilla/Telemetry.h" |
11 | | #include "nsCOMPtr.h" |
12 | | #include "nsAutoPtr.h" |
13 | | #include "nsString.h" |
14 | | #include <algorithm> |
15 | | #include "mozilla/Unused.h" |
16 | | |
17 | | |
18 | | namespace mozilla { |
19 | | namespace net { |
20 | | namespace CacheFileUtils { |
21 | | |
22 | | // This designates the format for the "alt-data" metadata. |
23 | | // When the format changes we need to update the version. |
24 | | static uint32_t const kAltDataVersion = 1; |
25 | | const char *kAltDataKey = "alt-data"; |
26 | | |
27 | | namespace { |
28 | | |
29 | | /** |
30 | | * A simple recursive descent parser for the mapping key. |
31 | | */ |
32 | | class KeyParser : protected Tokenizer |
33 | | { |
34 | | public: |
35 | | explicit KeyParser(nsACString const& aInput) |
36 | | : Tokenizer(aInput) |
37 | | , isAnonymous(false) |
38 | | // Initialize the cache key to a zero length by default |
39 | | , lastTag(0) |
40 | 0 | { |
41 | 0 | } |
42 | | |
43 | | private: |
44 | | // Results |
45 | | OriginAttributes originAttribs; |
46 | | bool isAnonymous; |
47 | | nsCString idEnhance; |
48 | | nsDependentCSubstring cacheKey; |
49 | | |
50 | | // Keeps the last tag name, used for alphabetical sort checking |
51 | | char lastTag; |
52 | | |
53 | | // Classifier for the 'tag' character valid range |
54 | | static bool TagChar(const char aChar) |
55 | 0 | { |
56 | 0 | return aChar >= ' ' && aChar <= '~'; |
57 | 0 | } |
58 | | |
59 | | bool ParseTags() |
60 | 0 | { |
61 | 0 | // Expects to be at the tag name or at the end |
62 | 0 | if (CheckEOF()) { |
63 | 0 | return true; |
64 | 0 | } |
65 | 0 | |
66 | 0 | char tag; |
67 | 0 | if (!ReadChar(&TagChar, &tag)) { |
68 | 0 | return false; |
69 | 0 | } |
70 | 0 | |
71 | 0 | // Check the alphabetical order, hard-fail on disobedience |
72 | 0 | if (!(lastTag < tag || tag == ':')) { |
73 | 0 | return false; |
74 | 0 | } |
75 | 0 | lastTag = tag; |
76 | 0 |
|
77 | 0 | switch (tag) { |
78 | 0 | case ':': |
79 | 0 | // last possible tag, when present there is the cacheKey following, |
80 | 0 | // not terminated with ',' and no need to unescape. |
81 | 0 | cacheKey.Rebind(mCursor, mEnd - mCursor); |
82 | 0 | return true; |
83 | 0 | case 'O': { |
84 | 0 | nsAutoCString originSuffix; |
85 | 0 | if (!ParseValue(&originSuffix) || !originAttribs.PopulateFromSuffix(originSuffix)) { |
86 | 0 | return false; |
87 | 0 | } |
88 | 0 | break; |
89 | 0 | } |
90 | 0 | case 'p': |
91 | 0 | originAttribs.SyncAttributesWithPrivateBrowsing(true); |
92 | 0 | break; |
93 | 0 | case 'b': |
94 | 0 | // Leaving to be able to read and understand oldformatted entries |
95 | 0 | originAttribs.mInIsolatedMozBrowser = true; |
96 | 0 | break; |
97 | 0 | case 'a': |
98 | 0 | isAnonymous = true; |
99 | 0 | break; |
100 | 0 | case 'i': { |
101 | 0 | // Leaving to be able to read and understand oldformatted entries |
102 | 0 | if (!ReadInteger(&originAttribs.mAppId)) { |
103 | 0 | return false; // not a valid 32-bit integer |
104 | 0 | } |
105 | 0 | break; |
106 | 0 | } |
107 | 0 | case '~': |
108 | 0 | if (!ParseValue(&idEnhance)) { |
109 | 0 | return false; |
110 | 0 | } |
111 | 0 | break; |
112 | 0 | default: |
113 | 0 | if (!ParseValue()) { // skip any tag values, optional |
114 | 0 | return false; |
115 | 0 | } |
116 | 0 | break; |
117 | 0 | } |
118 | 0 | |
119 | 0 | // We expect a comma after every tag |
120 | 0 | if (!CheckChar(',')) { |
121 | 0 | return false; |
122 | 0 | } |
123 | 0 | |
124 | 0 | // Recurse to the next tag |
125 | 0 | return ParseTags(); |
126 | 0 | } |
127 | | |
128 | | bool ParseValue(nsACString *result = nullptr) |
129 | 0 | { |
130 | 0 | // If at the end, fail since we expect a comma ; value may be empty tho |
131 | 0 | if (CheckEOF()) { |
132 | 0 | return false; |
133 | 0 | } |
134 | 0 | |
135 | 0 | Token t; |
136 | 0 | while (Next(t)) { |
137 | 0 | if (!Token::Char(',').Equals(t)) { |
138 | 0 | if (result) { |
139 | 0 | result->Append(t.Fragment()); |
140 | 0 | } |
141 | 0 | continue; |
142 | 0 | } |
143 | 0 |
|
144 | 0 | if (CheckChar(',')) { |
145 | 0 | // Two commas in a row, escaping |
146 | 0 | if (result) { |
147 | 0 | result->Append(','); |
148 | 0 | } |
149 | 0 | continue; |
150 | 0 | } |
151 | 0 |
|
152 | 0 | // We must give the comma back since the upper calls expect it |
153 | 0 | Rollback(); |
154 | 0 | return true; |
155 | 0 | } |
156 | 0 |
|
157 | 0 | return false; |
158 | 0 | } |
159 | | |
160 | | public: |
161 | | already_AddRefed<LoadContextInfo> Parse() |
162 | 0 | { |
163 | 0 | RefPtr<LoadContextInfo> info; |
164 | 0 | if (ParseTags()) { |
165 | 0 | info = GetLoadContextInfo(isAnonymous, originAttribs); |
166 | 0 | } |
167 | 0 |
|
168 | 0 | return info.forget(); |
169 | 0 | } |
170 | | |
171 | | void URISpec(nsACString &result) |
172 | 0 | { |
173 | 0 | result.Assign(cacheKey); |
174 | 0 | } |
175 | | |
176 | | void IdEnhance(nsACString &result) |
177 | 0 | { |
178 | 0 | result.Assign(idEnhance); |
179 | 0 | } |
180 | | }; |
181 | | |
182 | | } // namespace |
183 | | |
184 | | already_AddRefed<nsILoadContextInfo> |
185 | | ParseKey(const nsACString& aKey, |
186 | | nsACString* aIdEnhance, |
187 | | nsACString* aURISpec) |
188 | 0 | { |
189 | 0 | KeyParser parser(aKey); |
190 | 0 | RefPtr<LoadContextInfo> info = parser.Parse(); |
191 | 0 |
|
192 | 0 | if (info) { |
193 | 0 | if (aIdEnhance) |
194 | 0 | parser.IdEnhance(*aIdEnhance); |
195 | 0 | if (aURISpec) |
196 | 0 | parser.URISpec(*aURISpec); |
197 | 0 | } |
198 | 0 |
|
199 | 0 | return info.forget(); |
200 | 0 | } |
201 | | |
202 | | void |
203 | | AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString &_retval) |
204 | 0 | { |
205 | 0 | /** |
206 | 0 | * This key is used to salt file hashes. When form of the key is changed |
207 | 0 | * cache entries will fail to find on disk. |
208 | 0 | * |
209 | 0 | * IMPORTANT NOTE: |
210 | 0 | * Keep the attributes list sorted according their ASCII code. |
211 | 0 | */ |
212 | 0 |
|
213 | 0 | OriginAttributes const *oa = aInfo->OriginAttributesPtr(); |
214 | 0 | nsAutoCString suffix; |
215 | 0 | oa->CreateSuffix(suffix); |
216 | 0 | if (!suffix.IsEmpty()) { |
217 | 0 | AppendTagWithValue(_retval, 'O', suffix); |
218 | 0 | } |
219 | 0 |
|
220 | 0 | if (aInfo->IsAnonymous()) { |
221 | 0 | _retval.AppendLiteral("a,"); |
222 | 0 | } |
223 | 0 |
|
224 | 0 | if (aInfo->IsPrivate()) { |
225 | 0 | _retval.AppendLiteral("p,"); |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | void |
230 | | AppendTagWithValue(nsACString& aTarget, char const aTag, const nsACString& aValue) |
231 | 0 | { |
232 | 0 | aTarget.Append(aTag); |
233 | 0 |
|
234 | 0 | // First check the value string to save some memory copying |
235 | 0 | // for cases we don't need to escape at all (most likely). |
236 | 0 | if (!aValue.IsEmpty()) { |
237 | 0 | if (!aValue.Contains(',')) { |
238 | 0 | // No need to escape |
239 | 0 | aTarget.Append(aValue); |
240 | 0 | } else { |
241 | 0 | nsAutoCString escapedValue(aValue); |
242 | 0 | escapedValue.ReplaceSubstring( |
243 | 0 | NS_LITERAL_CSTRING(","), NS_LITERAL_CSTRING(",,")); |
244 | 0 | aTarget.Append(escapedValue); |
245 | 0 | } |
246 | 0 | } |
247 | 0 |
|
248 | 0 | aTarget.Append(','); |
249 | 0 | } |
250 | | |
251 | | nsresult |
252 | | KeyMatchesLoadContextInfo(const nsACString &aKey, nsILoadContextInfo *aInfo, |
253 | | bool *_retval) |
254 | 0 | { |
255 | 0 | nsCOMPtr<nsILoadContextInfo> info = ParseKey(aKey); |
256 | 0 |
|
257 | 0 | if (!info) { |
258 | 0 | return NS_ERROR_FAILURE; |
259 | 0 | } |
260 | 0 | |
261 | 0 | *_retval = info->Equals(aInfo); |
262 | 0 | return NS_OK; |
263 | 0 | } |
264 | | |
265 | | ValidityPair::ValidityPair(uint32_t aOffset, uint32_t aLen) |
266 | | : mOffset(aOffset), mLen(aLen) |
267 | 0 | {} |
268 | | |
269 | | bool |
270 | | ValidityPair::CanBeMerged(const ValidityPair& aOther) const |
271 | 0 | { |
272 | 0 | // The pairs can be merged into a single one if the start of one of the pairs |
273 | 0 | // is placed anywhere in the validity interval of other pair or exactly after |
274 | 0 | // its end. |
275 | 0 | return IsInOrFollows(aOther.mOffset) || aOther.IsInOrFollows(mOffset); |
276 | 0 | } |
277 | | |
278 | | bool |
279 | | ValidityPair::IsInOrFollows(uint32_t aOffset) const |
280 | 0 | { |
281 | 0 | return mOffset <= aOffset && mOffset + mLen >= aOffset; |
282 | 0 | } |
283 | | |
284 | | bool |
285 | | ValidityPair::LessThan(const ValidityPair& aOther) const |
286 | 0 | { |
287 | 0 | if (mOffset < aOther.mOffset) { |
288 | 0 | return true; |
289 | 0 | } |
290 | 0 | |
291 | 0 | if (mOffset == aOther.mOffset && mLen < aOther.mLen) { |
292 | 0 | return true; |
293 | 0 | } |
294 | 0 | |
295 | 0 | return false; |
296 | 0 | } |
297 | | |
298 | | void |
299 | | ValidityPair::Merge(const ValidityPair& aOther) |
300 | 0 | { |
301 | 0 | MOZ_ASSERT(CanBeMerged(aOther)); |
302 | 0 |
|
303 | 0 | uint32_t offset = std::min(mOffset, aOther.mOffset); |
304 | 0 | uint32_t end = std::max(mOffset + mLen, aOther.mOffset + aOther.mLen); |
305 | 0 |
|
306 | 0 | mOffset = offset; |
307 | 0 | mLen = end - offset; |
308 | 0 | } |
309 | | |
310 | | void |
311 | | ValidityMap::Log() const |
312 | 0 | { |
313 | 0 | LOG(("ValidityMap::Log() - number of pairs: %zu", mMap.Length())); |
314 | 0 | for (uint32_t i=0; i<mMap.Length(); i++) { |
315 | 0 | LOG((" (%u, %u)", mMap[i].Offset() + 0, mMap[i].Len() + 0)); |
316 | 0 | } |
317 | 0 | } |
318 | | |
319 | | uint32_t |
320 | | ValidityMap::Length() const |
321 | 0 | { |
322 | 0 | return mMap.Length(); |
323 | 0 | } |
324 | | |
325 | | void |
326 | | ValidityMap::AddPair(uint32_t aOffset, uint32_t aLen) |
327 | 0 | { |
328 | 0 | ValidityPair pair(aOffset, aLen); |
329 | 0 |
|
330 | 0 | if (mMap.Length() == 0) { |
331 | 0 | mMap.AppendElement(pair); |
332 | 0 | return; |
333 | 0 | } |
334 | 0 | |
335 | 0 | // Find out where to place this pair into the map, it can overlap only with |
336 | 0 | // one preceding pair and all subsequent pairs. |
337 | 0 | uint32_t pos = 0; |
338 | 0 | for (pos = mMap.Length(); pos > 0; ) { |
339 | 0 | --pos; |
340 | 0 |
|
341 | 0 | if (mMap[pos].LessThan(pair)) { |
342 | 0 | // The new pair should be either inserted after pos or merged with it. |
343 | 0 | if (mMap[pos].CanBeMerged(pair)) { |
344 | 0 | // Merge with the preceding pair |
345 | 0 | mMap[pos].Merge(pair); |
346 | 0 | } else { |
347 | 0 | // They don't overlap, element must be placed after pos element |
348 | 0 | ++pos; |
349 | 0 | if (pos == mMap.Length()) { |
350 | 0 | mMap.AppendElement(pair); |
351 | 0 | } else { |
352 | 0 | mMap.InsertElementAt(pos, pair); |
353 | 0 | } |
354 | 0 | } |
355 | 0 |
|
356 | 0 | break; |
357 | 0 | } |
358 | 0 |
|
359 | 0 | if (pos == 0) { |
360 | 0 | // The new pair should be placed in front of all existing pairs. |
361 | 0 | mMap.InsertElementAt(0, pair); |
362 | 0 | } |
363 | 0 | } |
364 | 0 |
|
365 | 0 | // pos now points to merged or inserted pair, check whether it overlaps with |
366 | 0 | // subsequent pairs. |
367 | 0 | while (pos + 1 < mMap.Length()) { |
368 | 0 | if (mMap[pos].CanBeMerged(mMap[pos + 1])) { |
369 | 0 | mMap[pos].Merge(mMap[pos + 1]); |
370 | 0 | mMap.RemoveElementAt(pos + 1); |
371 | 0 | } else { |
372 | 0 | break; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | | void |
378 | | ValidityMap::Clear() |
379 | 0 | { |
380 | 0 | mMap.Clear(); |
381 | 0 | } |
382 | | |
383 | | size_t |
384 | | ValidityMap::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const |
385 | 0 | { |
386 | 0 | return mMap.ShallowSizeOfExcludingThis(mallocSizeOf); |
387 | 0 | } |
388 | | |
389 | | ValidityPair& |
390 | | ValidityMap::operator[](uint32_t aIdx) |
391 | 0 | { |
392 | 0 | return mMap.ElementAt(aIdx); |
393 | 0 | } |
394 | | |
395 | | StaticMutex DetailedCacheHitTelemetry::sLock; |
396 | | uint32_t DetailedCacheHitTelemetry::sRecordCnt = 0; |
397 | | DetailedCacheHitTelemetry::HitRate DetailedCacheHitTelemetry::sHRStats[kNumOfRanges]; |
398 | | |
399 | | DetailedCacheHitTelemetry::HitRate::HitRate() |
400 | 60 | { |
401 | 60 | Reset(); |
402 | 60 | } |
403 | | |
404 | | void |
405 | | DetailedCacheHitTelemetry::HitRate::AddRecord(ERecType aType) |
406 | 0 | { |
407 | 0 | if (aType == HIT) { |
408 | 0 | ++mHitCnt; |
409 | 0 | } else { |
410 | 0 | ++mMissCnt; |
411 | 0 | } |
412 | 0 | } |
413 | | |
414 | | uint32_t |
415 | | DetailedCacheHitTelemetry::HitRate::GetHitRateBucket(uint32_t aNumOfBuckets) const |
416 | 0 | { |
417 | 0 | uint32_t bucketIdx = (aNumOfBuckets * mHitCnt) / (mHitCnt + mMissCnt); |
418 | 0 | if (bucketIdx == aNumOfBuckets) { // make sure 100% falls into the last bucket |
419 | 0 | --bucketIdx; |
420 | 0 | } |
421 | 0 |
|
422 | 0 | return bucketIdx; |
423 | 0 | } |
424 | | |
425 | | uint32_t |
426 | | DetailedCacheHitTelemetry::HitRate::Count() |
427 | 0 | { |
428 | 0 | return mHitCnt + mMissCnt; |
429 | 0 | } |
430 | | |
431 | | void |
432 | | DetailedCacheHitTelemetry::HitRate::Reset() |
433 | 60 | { |
434 | 60 | mHitCnt = 0; |
435 | 60 | mMissCnt = 0; |
436 | 60 | } |
437 | | |
438 | | // static |
439 | | void |
440 | | DetailedCacheHitTelemetry::AddRecord(ERecType aType, TimeStamp aLoadStart) |
441 | 0 | { |
442 | 0 | bool isUpToDate = false; |
443 | 0 | CacheIndex::IsUpToDate(&isUpToDate); |
444 | 0 | if (!isUpToDate) { |
445 | 0 | // Ignore the record when the entry file count might be incorrect |
446 | 0 | return; |
447 | 0 | } |
448 | 0 | |
449 | 0 | uint32_t entryCount; |
450 | 0 | nsresult rv = CacheIndex::GetEntryFileCount(&entryCount); |
451 | 0 | if (NS_FAILED(rv)) { |
452 | 0 | return; |
453 | 0 | } |
454 | 0 | |
455 | 0 | uint32_t rangeIdx = entryCount / kRangeSize; |
456 | 0 | if (rangeIdx >= kNumOfRanges) { // The last range has no upper limit. |
457 | 0 | rangeIdx = kNumOfRanges - 1; |
458 | 0 | } |
459 | 0 |
|
460 | 0 | uint32_t hitMissValue = 2 * rangeIdx; // 2 values per range |
461 | 0 | if (aType == MISS) { // The order is HIT, MISS |
462 | 0 | ++hitMissValue; |
463 | 0 | } |
464 | 0 |
|
465 | 0 | StaticMutexAutoLock lock(sLock); |
466 | 0 |
|
467 | 0 | if (aType == MISS) { |
468 | 0 | mozilla::Telemetry::AccumulateTimeDelta( |
469 | 0 | mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS, |
470 | 0 | aLoadStart); |
471 | 0 | } else { |
472 | 0 | mozilla::Telemetry::AccumulateTimeDelta( |
473 | 0 | mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS, |
474 | 0 | aLoadStart); |
475 | 0 | } |
476 | 0 |
|
477 | 0 | Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_MISS_STAT_PER_CACHE_SIZE, |
478 | 0 | hitMissValue); |
479 | 0 |
|
480 | 0 | sHRStats[rangeIdx].AddRecord(aType); |
481 | 0 | ++sRecordCnt; |
482 | 0 |
|
483 | 0 | if (sRecordCnt < kTotalSamplesReportLimit) { |
484 | 0 | return; |
485 | 0 | } |
486 | 0 | |
487 | 0 | sRecordCnt = 0; |
488 | 0 |
|
489 | 0 | for (uint32_t i = 0; i < kNumOfRanges; ++i) { |
490 | 0 | if (sHRStats[i].Count() >= kHitRateSamplesReportLimit) { |
491 | 0 | // The telemetry enums are grouped by buckets as follows: |
492 | 0 | // Telemetry value : 0,1,2,3, ... ,19,20,21,22, ... ,398,399 |
493 | 0 | // Hit rate bucket : 0,0,0,0, ... , 0, 1, 1, 1, ... , 19, 19 |
494 | 0 | // Cache size range: 0,1,2,3, ... ,19, 0, 1, 2, ... , 18, 19 |
495 | 0 | uint32_t bucketOffset = sHRStats[i].GetHitRateBucket(kHitRateBuckets) * |
496 | 0 | kNumOfRanges; |
497 | 0 |
|
498 | 0 | Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_RATE_PER_CACHE_SIZE, |
499 | 0 | bucketOffset + i); |
500 | 0 | sHRStats[i].Reset(); |
501 | 0 | } |
502 | 0 | } |
503 | 0 | } |
504 | | |
505 | | StaticMutex CachePerfStats::sLock; |
506 | | CachePerfStats::PerfData CachePerfStats::sData[CachePerfStats::LAST]; |
507 | | uint32_t CachePerfStats::sCacheSlowCnt = 0; |
508 | | uint32_t CachePerfStats::sCacheNotSlowCnt = 0; |
509 | | |
510 | | CachePerfStats::MMA::MMA(uint32_t aTotalWeight, bool aFilter) |
511 | | : mSum(0) |
512 | | , mSumSq(0) |
513 | | , mCnt(0) |
514 | | , mWeight(aTotalWeight) |
515 | | , mFilter(aFilter) |
516 | 24 | { |
517 | 24 | } |
518 | | |
519 | | void |
520 | | CachePerfStats::MMA::AddValue(uint32_t aValue) |
521 | 0 | { |
522 | 0 | if (mFilter) { |
523 | 0 | // Filter high spikes |
524 | 0 | uint32_t avg = GetAverage(); |
525 | 0 | uint32_t stddev = GetStdDev(); |
526 | 0 | uint32_t maxdiff = avg + (3 * stddev); |
527 | 0 | if (avg && aValue > avg + maxdiff) { |
528 | 0 | return; |
529 | 0 | } |
530 | 0 | } |
531 | 0 | |
532 | 0 | if (mCnt < mWeight) { |
533 | 0 | // Compute arithmetic average until we have at least mWeight values |
534 | 0 | CheckedInt<uint64_t> newSumSq = CheckedInt<uint64_t>(aValue) * aValue; |
535 | 0 | newSumSq += mSumSq; |
536 | 0 | if (!newSumSq.isValid()) { |
537 | 0 | return; // ignore this value |
538 | 0 | } |
539 | 0 | mSumSq = newSumSq.value(); |
540 | 0 | mSum += aValue; |
541 | 0 | ++mCnt; |
542 | 0 | } else { |
543 | 0 | CheckedInt<uint64_t> newSumSq = mSumSq - mSumSq / mCnt; |
544 | 0 | newSumSq += static_cast<uint64_t>(aValue) * aValue; |
545 | 0 | if (!newSumSq.isValid()) { |
546 | 0 | return; // ignore this value |
547 | 0 | } |
548 | 0 | mSumSq = newSumSq.value(); |
549 | 0 |
|
550 | 0 | // Compute modified moving average for more values: |
551 | 0 | // newAvg = ((weight - 1) * oldAvg + newValue) / weight |
552 | 0 | mSum -= GetAverage(); |
553 | 0 | mSum += aValue; |
554 | 0 | } |
555 | 0 | } |
556 | | |
557 | | uint32_t |
558 | | CachePerfStats::MMA::GetAverage() |
559 | 0 | { |
560 | 0 | if (mCnt == 0) { |
561 | 0 | return 0; |
562 | 0 | } |
563 | 0 | |
564 | 0 | return mSum / mCnt; |
565 | 0 | } |
566 | | |
567 | | uint32_t |
568 | | CachePerfStats::MMA::GetStdDev() |
569 | 0 | { |
570 | 0 | if (mCnt == 0) { |
571 | 0 | return 0; |
572 | 0 | } |
573 | 0 | |
574 | 0 | uint32_t avg = GetAverage(); |
575 | 0 | uint64_t avgSq = static_cast<uint64_t>(avg) * avg; |
576 | 0 | uint64_t variance = mSumSq / mCnt; |
577 | 0 | if (variance < avgSq) { |
578 | 0 | // Due to rounding error when using integer data type, it can happen that |
579 | 0 | // average of squares of the values is smaller than square of the average |
580 | 0 | // of the values. In this case fix mSumSq. |
581 | 0 | variance = avgSq; |
582 | 0 | mSumSq = variance * mCnt; |
583 | 0 | } |
584 | 0 |
|
585 | 0 | variance -= avgSq; |
586 | 0 | return sqrt(static_cast<double>(variance)); |
587 | 0 | } |
588 | | |
589 | | CachePerfStats::PerfData::PerfData() |
590 | | : mFilteredAvg(50, true) |
591 | | , mShortAvg(3, false) |
592 | 12 | { |
593 | 12 | } |
594 | | |
595 | | void |
596 | | CachePerfStats::PerfData::AddValue(uint32_t aValue, bool aShortOnly) |
597 | 0 | { |
598 | 0 | if (!aShortOnly) { |
599 | 0 | mFilteredAvg.AddValue(aValue); |
600 | 0 | } |
601 | 0 | mShortAvg.AddValue(aValue); |
602 | 0 | } |
603 | | |
604 | | uint32_t |
605 | | CachePerfStats::PerfData::GetAverage(bool aFiltered) |
606 | 0 | { |
607 | 0 | return aFiltered ? mFilteredAvg.GetAverage() : mShortAvg.GetAverage(); |
608 | 0 | } |
609 | | |
610 | | uint32_t |
611 | | CachePerfStats::PerfData::GetStdDev(bool aFiltered) |
612 | 0 | { |
613 | 0 | return aFiltered ? mFilteredAvg.GetStdDev() : mShortAvg.GetStdDev(); |
614 | 0 | } |
615 | | |
616 | | // static |
617 | | void |
618 | | CachePerfStats::AddValue(EDataType aType, uint32_t aValue, bool aShortOnly) |
619 | 0 | { |
620 | 0 | StaticMutexAutoLock lock(sLock); |
621 | 0 | sData[aType].AddValue(aValue, aShortOnly); |
622 | 0 | } |
623 | | |
624 | | // static |
625 | | uint32_t |
626 | | CachePerfStats::GetAverage(EDataType aType, bool aFiltered) |
627 | 0 | { |
628 | 0 | StaticMutexAutoLock lock(sLock); |
629 | 0 | return sData[aType].GetAverage(aFiltered); |
630 | 0 | } |
631 | | |
632 | | // static |
633 | | uint32_t |
634 | | CachePerfStats::GetStdDev(EDataType aType, bool aFiltered) |
635 | 0 | { |
636 | 0 | StaticMutexAutoLock lock(sLock); |
637 | 0 | return sData[aType].GetStdDev(aFiltered); |
638 | 0 | } |
639 | | |
640 | | //static |
641 | | bool |
642 | | CachePerfStats::IsCacheSlow() |
643 | 0 | { |
644 | 0 | // Compare mShortAvg with mFilteredAvg to find out whether cache is getting |
645 | 0 | // slower. Use only data about single IO operations because ENTRY_OPEN can be |
646 | 0 | // affected by more factors than a slow disk. |
647 | 0 | for (uint32_t i = 0; i < ENTRY_OPEN; ++i) { |
648 | 0 | if (i == IO_WRITE) { |
649 | 0 | // Skip this data type. IsCacheSlow is used for determining cache slowness |
650 | 0 | // when opening entries. Writes have low priority and it's normal that |
651 | 0 | // they are delayed a lot, but this doesn't necessarily affect opening |
652 | 0 | // cache entries. |
653 | 0 | continue; |
654 | 0 | } |
655 | 0 | |
656 | 0 | uint32_t avgLong = sData[i].GetAverage(true); |
657 | 0 | if (avgLong == 0) { |
658 | 0 | // We have no perf data yet, skip this data type. |
659 | 0 | continue; |
660 | 0 | } |
661 | 0 | uint32_t avgShort = sData[i].GetAverage(false); |
662 | 0 | uint32_t stddevLong = sData[i].GetStdDev(true); |
663 | 0 | uint32_t maxdiff = avgLong + (3 * stddevLong); |
664 | 0 |
|
665 | 0 | if (avgShort > avgLong + maxdiff) { |
666 | 0 | LOG(("CachePerfStats::IsCacheSlow() - result is slow based on perf " |
667 | 0 | "type %u [avgShort=%u, avgLong=%u, stddevLong=%u]", i, avgShort, |
668 | 0 | avgLong, stddevLong)); |
669 | 0 | ++sCacheSlowCnt; |
670 | 0 | return true; |
671 | 0 | } |
672 | 0 | } |
673 | 0 |
|
674 | 0 | ++sCacheNotSlowCnt; |
675 | 0 | return false; |
676 | 0 | } |
677 | | |
678 | | //static |
679 | | void |
680 | | CachePerfStats::GetSlowStats(uint32_t *aSlow, uint32_t *aNotSlow) |
681 | 0 | { |
682 | 0 | *aSlow = sCacheSlowCnt; |
683 | 0 | *aNotSlow = sCacheNotSlowCnt; |
684 | 0 | } |
685 | | |
686 | | void |
687 | 0 | FreeBuffer(void *aBuf) { |
688 | 0 | #ifndef NS_FREE_PERMANENT_DATA |
689 | 0 | if (CacheObserver::ShuttingDown()) { |
690 | 0 | return; |
691 | 0 | } |
692 | 0 | #endif |
693 | 0 | |
694 | 0 | free(aBuf); |
695 | 0 | } |
696 | | |
697 | | nsresult |
698 | | ParseAlternativeDataInfo(const char *aInfo, int64_t *_offset, nsACString *_type) |
699 | 0 | { |
700 | 0 | // The format is: "1;12345,javascript/binary" |
701 | 0 | // <version>;<offset>,<type> |
702 | 0 | mozilla::Tokenizer p(aInfo, nullptr, "/"); |
703 | 0 | uint32_t altDataVersion = 0; |
704 | 0 | int64_t altDataOffset = -1; |
705 | 0 |
|
706 | 0 | // The metadata format has a wrong version number. |
707 | 0 | if (!p.ReadInteger(&altDataVersion) || |
708 | 0 | altDataVersion != kAltDataVersion) { |
709 | 0 | LOG(("ParseAlternativeDataInfo() - altDataVersion=%u, " |
710 | 0 | "expectedVersion=%u", altDataVersion, kAltDataVersion)); |
711 | 0 | return NS_ERROR_NOT_AVAILABLE; |
712 | 0 | } |
713 | 0 |
|
714 | 0 | if (!p.CheckChar(';') || |
715 | 0 | !p.ReadInteger(&altDataOffset) || |
716 | 0 | !p.CheckChar(',')) { |
717 | 0 | return NS_ERROR_NOT_AVAILABLE; |
718 | 0 | } |
719 | 0 | |
720 | 0 | // The requested alt-data representation is not available |
721 | 0 | if (altDataOffset < 0) { |
722 | 0 | return NS_ERROR_NOT_AVAILABLE; |
723 | 0 | } |
724 | 0 | |
725 | 0 | if (_offset) { |
726 | 0 | *_offset = altDataOffset; |
727 | 0 | } |
728 | 0 |
|
729 | 0 | if (_type) { |
730 | 0 | mozilla::Unused << p.ReadUntil(Tokenizer::Token::EndOfFile(), *_type); |
731 | 0 | } |
732 | 0 |
|
733 | 0 | return NS_OK; |
734 | 0 | } |
735 | | |
736 | | void |
737 | | BuildAlternativeDataInfo(const char *aInfo, int64_t aOffset, nsACString &_retval) |
738 | 0 | { |
739 | 0 | _retval.Truncate(); |
740 | 0 | _retval.AppendInt(kAltDataVersion); |
741 | 0 | _retval.Append(';'); |
742 | 0 | _retval.AppendInt(aOffset); |
743 | 0 | _retval.Append(','); |
744 | 0 | _retval.Append(aInfo); |
745 | 0 | } |
746 | | |
747 | | } // namespace CacheFileUtils |
748 | | } // namespace net |
749 | | } // namespace mozilla |