// // ASEventLog.mm // Texture // // Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. // Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // #import #import #import #import @implementation ASEventLog { AS::RecursiveMutex __instanceLock__; // The index of the most recent log entry. -1 until first entry. NSInteger _eventLogHead; // A description of the object we're logging for. This is immutable. NSString *_objectDescription; } /** * Even just when debugging, all these events can take up considerable memory. * Store them in a shared NSCache to limit the total consumption. */ + (NSCache *> *)contentsCache { static NSCache *cache; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ cache = [[NSCache alloc] init]; }); return cache; } - (instancetype)initWithObject:(id)anObject { if ((self = [super init])) { _objectDescription = ASObjectDescriptionMakeTiny(anObject); _eventLogHead = -1; } return self; } - (instancetype)init { // This method is marked unavailable so the compiler won't let them call it. ASDisplayNodeFailAssert(@"Failed to call initWithObject:"); return nil; } - (void)logEventWithBacktrace:(NSArray *)backtrace format:(NSString *)format, ... { va_list args; va_start(args, format); ASTraceEvent *event = [[ASTraceEvent alloc] initWithBacktrace:backtrace format:format arguments:args]; va_end(args); AS::MutexLocker l(__instanceLock__); NSCache *cache = [ASEventLog contentsCache]; NSMutableArray *events = [cache objectForKey:self]; if (events == nil) { events = [NSMutableArray arrayWithObject:event]; [cache setObject:events forKey:self]; _eventLogHead = 0; return; } // Increment the head index. _eventLogHead = (_eventLogHead + 1) % ASEVENTLOG_CAPACITY; if (_eventLogHead < events.count) { [events replaceObjectAtIndex:_eventLogHead withObject:event]; } else { [events insertObject:event atIndex:_eventLogHead]; } } - (NSArray *)events { NSMutableArray *events = [[ASEventLog contentsCache] objectForKey:self]; if (events == nil) { return nil; } AS::MutexLocker l(__instanceLock__); NSUInteger tail = (_eventLogHead + 1); NSUInteger count = events.count; NSMutableArray *result = [NSMutableArray array]; // Start from `tail` and go through array, wrapping around when we exceed end index. for (NSUInteger actualIndex = 0; actualIndex < ASEVENTLOG_CAPACITY; actualIndex++) { NSInteger ringIndex = (tail + actualIndex) % ASEVENTLOG_CAPACITY; if (ringIndex < count) { [result addObject:events[ringIndex]]; } } return result; } - (NSString *)description { /** * This description intentionally doesn't follow the standard description format. * Since this is a log, it's important for the description to look a certain way, and * the formal description style doesn't allow for newlines and has a ton of punctuation. */ NSArray *events = [self events]; if (events == nil) { return [NSString stringWithFormat:@"Event log for %@ was purged to conserve memory.", _objectDescription]; } else { return [NSString stringWithFormat:@"Event log for %@. Events: %@", _objectDescription, events]; } } @end