Skip to content
This repository has been archived by the owner on Nov 1, 2021. It is now read-only.

Avoid use of CFAbsoluteTime as much as possible. #87

Merged
merged 2 commits into from
Nov 27, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 25 additions & 5 deletions SPDY.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,18 @@
5C2229591952257800CAF160 /* SPDYURLRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2229581952257800CAF160 /* SPDYURLRequestTest.m */; };
5C2A211D19F9CA0E00D0EA76 /* SPDYLoggingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2A211C19F9CA0E00D0EA76 /* SPDYLoggingTest.m */; };
5C427F0F1A1C7C4D0072403D /* SPDYSenTestLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C427F0E1A1C7C4D0072403D /* SPDYSenTestLog.m */; };
5C427F111A1D57890072403D /* SPDYStopwatchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C427F101A1D57890072403D /* SPDYStopwatchTest.m */; };
5CF0A2C91A089BC500B6D141 /* SPDYMetadataTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF0A2C81A089BC500B6D141 /* SPDYMetadataTest.m */; };
5CF0A2CC1A0952D900B6D141 /* SPDYMockURLProtocolClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF0A2CB1A0952D900B6D141 /* SPDYMockURLProtocolClient.m */; };
7774C1318AB029C6BCEF84D6 /* SPDYSessionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C2026A4DE9957D75F629 /* SPDYSessionTest.m */; };
7774C1F1E544793907908882 /* SPDYMockFrameEncoderDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C69089A6978113F0C275 /* SPDYMockFrameEncoderDelegate.m */; };
7774C868441241542B0A90C0 /* SPDYStopwatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774CE34A3AA067D98DA7ECC /* SPDYStopwatch.m */; };
7774CA1FA1F4A59CA0906BB7 /* SPDYSocket+SPDYSocketMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774C0ECD0C6E5D73FB38752 /* SPDYSocket+SPDYSocketMock.m */; };
7774CC29BBD86413798C1425 /* SPDYStopwatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 7774CE030BB898D2BE3D320D /* SPDYStopwatch.h */; };
7774CD12A73EA9ABAE521441 /* SPDYStopwatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774CE34A3AA067D98DA7ECC /* SPDYStopwatch.m */; };
7774CD9416661E40D76713F5 /* SPDYStopwatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 7774CE030BB898D2BE3D320D /* SPDYStopwatch.h */; };
7774CDD84A5D07F8DE5B8684 /* SPDYStopwatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 7774CE34A3AA067D98DA7ECC /* SPDYStopwatch.m */; };
7774CF887055793F373F0D5E /* SPDYStopwatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 7774CE030BB898D2BE3D320D /* SPDYStopwatch.h */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -152,6 +159,7 @@
5C2229581952257800CAF160 /* SPDYURLRequestTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYURLRequestTest.m; sourceTree = "<group>"; };
5C2A211C19F9CA0E00D0EA76 /* SPDYLoggingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYLoggingTest.m; sourceTree = "<group>"; };
5C427F0E1A1C7C4D0072403D /* SPDYSenTestLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYSenTestLog.m; sourceTree = "<group>"; };
5C427F101A1D57890072403D /* SPDYStopwatchTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYStopwatchTest.m; sourceTree = "<group>"; };
5CF0A2C81A089BC500B6D141 /* SPDYMetadataTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYMetadataTest.m; sourceTree = "<group>"; };
5CF0A2CA1A0952BA00B6D141 /* SPDYMockURLProtocolClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYMockURLProtocolClient.h; sourceTree = "<group>"; };
5CF0A2CB1A0952D900B6D141 /* SPDYMockURLProtocolClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYMockURLProtocolClient.m; sourceTree = "<group>"; };
Expand All @@ -160,6 +168,8 @@
7774C69089A6978113F0C275 /* SPDYMockFrameEncoderDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYMockFrameEncoderDelegate.m; sourceTree = "<group>"; };
7774C7E1AF717FC36B7F15B6 /* SPDYSocket+SPDYSocketMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SPDYSocket+SPDYSocketMock.h"; sourceTree = "<group>"; };
7774CD0A3295C8E314D6E3FF /* SPDYMockFrameEncoderDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYMockFrameEncoderDelegate.h; sourceTree = "<group>"; };
7774CE030BB898D2BE3D320D /* SPDYStopwatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYStopwatch.h; sourceTree = "<group>"; };
7774CE34A3AA067D98DA7ECC /* SPDYStopwatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDYStopwatch.m; sourceTree = "<group>"; };
D2CC14B216179B43002E37CF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
D2CC14B816179B43002E37CF /* SPDY-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SPDY-Prefix.pch"; sourceTree = "<group>"; };
D2CC14C01618CF62002E37CF /* SPDYProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDYProtocol.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -234,6 +244,7 @@
0679F3CE186217FC006F122E /* SPDYOriginTest.m */,
25959A3E1937DE3900FC9731 /* SPDYSessionManagerTest.m */,
7774C2026A4DE9957D75F629 /* SPDYSessionTest.m */,
5C427F101A1D57890072403D /* SPDYStopwatchTest.m */,
060C235D17CE9FCE000B4E9C /* SPDYStreamManagerTest.m */,
067EBFE617418F350029F16C /* SPDYStreamTest.m */,
5C2229581952257800CAF160 /* SPDYURLRequestTest.m */,
Expand Down Expand Up @@ -320,8 +331,8 @@
D2CC14B816179B43002E37CF /* SPDY-Prefix.pch */,
062EA63E175D4CD3003BC1CE /* SPDYCommonLogger.h */,
062EA63F175D4CD3003BC1CE /* SPDYCommonLogger.m */,
06811C961714D426000D1677 /* SPDYError.h */,
062EA63C175D1A15003BC1CE /* SPDYDefinitions.h */,
06811C961714D426000D1677 /* SPDYError.h */,
D2CC14C9161A25CC002E37CF /* SPDYFrame.h */,
D2CC14CA161A25CC002E37CF /* SPDYFrame.m */,
D2CC14C6161A1952002E37CF /* SPDYFrameDecoder.h */,
Expand All @@ -333,6 +344,8 @@
4FE891C7065B348CC7EF4BFC /* SPDYHeaderBlockDecompressor.h */,
4FE895BE087AC28BBBC857F9 /* SPDYHeaderBlockDecompressor.m */,
06811C9A1715DC85000D1677 /* SPDYLogger.h */,
0655606619D5EDA600631121 /* SPDYMetadata.h */,
0655606719D5EDA600631121 /* SPDYMetadata.m */,
06B290CC1861018900540A03 /* SPDYOrigin.h */,
06B290CD1861018900540A03 /* SPDYOrigin.m */,
D2CC14C01618CF62002E37CF /* SPDYProtocol.h */,
Expand All @@ -343,16 +356,16 @@
D2CC14CD161A5826002E37CF /* SPDYSessionManager.m */,
06FC94121694B92400FC95DF /* SPDYSettingsStore.h */,
06FC94131694B92400FC95DF /* SPDYSettingsStore.m */,
069D0E88167F9D010037D8AF /* SPDYStream.h */,
069D0E89167F9D010037D8AF /* SPDYStream.m */,
D2CC14CF161A9EE9002E37CF /* SPDYSocket.h */,
D2CC14D0161A9EE9002E37CF /* SPDYSocket.m */,
7774CE030BB898D2BE3D320D /* SPDYStopwatch.h */,
7774CE34A3AA067D98DA7ECC /* SPDYStopwatch.m */,
069D0E88167F9D010037D8AF /* SPDYStream.h */,
069D0E89167F9D010037D8AF /* SPDYStream.m */,
061C8E9217C5954400D22083 /* SPDYStreamManager.h */,
061C8E9317C5954400D22083 /* SPDYStreamManager.m */,
06E7BF111823B74D004DB65D /* SPDYTLSTrustEvaluator.h */,
06290990169E497300E35A82 /* SPDYZLibCommon.h */,
0655606619D5EDA600631121 /* SPDYMetadata.h */,
0655606719D5EDA600631121 /* SPDYMetadata.m */,
);
path = SPDY;
sourceTree = "<group>";
Expand All @@ -370,6 +383,7 @@
0540DAAF19CB802600673796 /* SPDYTLSTrustEvaluator.h in Headers */,
0540DAAC19CB7FF900673796 /* SPDYError.h in Headers */,
0540DAA919CB7FEB00673796 /* SPDYCommonLogger.h in Headers */,
7774CD9416661E40D76713F5 /* SPDYStopwatch.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -383,6 +397,7 @@
05C7CEBB19CB45820032D681 /* SPDYProtocol.h in Headers */,
05C7CEBC19CB458F0032D681 /* SPDYTLSTrustEvaluator.h in Headers */,
0540DAAA19CB7FEB00673796 /* SPDYCommonLogger.h in Headers */,
7774CC29BBD86413798C1425 /* SPDYStopwatch.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -396,6 +411,7 @@
06E7BF131824371F004DB65D /* SPDYTLSTrustEvaluator.h in Headers */,
06811C971714D426000D1677 /* SPDYError.h in Headers */,
062EA640175D4CD3003BC1CE /* SPDYCommonLogger.h in Headers */,
7774CF887055793F373F0D5E /* SPDYStopwatch.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -580,6 +596,7 @@
5CF0A2C91A089BC500B6D141 /* SPDYMetadataTest.m in Sources */,
060C235E17CE9FCE000B4E9C /* SPDYStreamManagerTest.m in Sources */,
0655606819D5EDA600631121 /* SPDYMetadata.m in Sources */,
5C427F111A1D57890072403D /* SPDYStopwatchTest.m in Sources */,
06290995169E4D9700E35A82 /* SPDYHeaderBlockCompressor.m in Sources */,
06FDA21216717DF100137DBD /* SPDYHeaderBlockDecompressor.m in Sources */,
5C2229591952257800CAF160 /* SPDYURLRequestTest.m in Sources */,
Expand All @@ -599,6 +616,7 @@
5C427F0F1A1C7C4D0072403D /* SPDYSenTestLog.m in Sources */,
7774CA1FA1F4A59CA0906BB7 /* SPDYSocket+SPDYSocketMock.m in Sources */,
7774C1318AB029C6BCEF84D6 /* SPDYSessionTest.m in Sources */,
7774CD12A73EA9ABAE521441 /* SPDYStopwatch.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -622,6 +640,7 @@
062EA643175D4CD3003BC1CE /* SPDYCommonLogger.m in Sources */,
061C8E9617C5954400D22083 /* SPDYStreamManager.m in Sources */,
06B290CF1861018A00540A03 /* SPDYOrigin.m in Sources */,
7774C868441241542B0A90C0 /* SPDYStopwatch.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -645,6 +664,7 @@
062EA645175D4CD3003BC1CE /* SPDYCommonLogger.m in Sources */,
061C8E9817C5954400D22083 /* SPDYStreamManager.m in Sources */,
06B290D21861018A00540A03 /* SPDYOrigin.m in Sources */,
7774CDD84A5D07F8DE5B8684 /* SPDYStopwatch.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
4 changes: 2 additions & 2 deletions SPDY/SPDYMetadata.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
// Created by Michael Schore
//

#import "SPDYCommonLogger.h"
#import "SPDYMetadata.h"
#import "SPDYProtocol.h"
#import "SPDYStopwatch.h"

@implementation SPDYMetadata
{
Expand Down Expand Up @@ -71,7 +71,7 @@ - (id)init
_hostPort = 0;

NSUInteger ptr = (NSUInteger)self;
CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent();
SPDYTimeInterval timestamp = [SPDYStopwatch currentSystemTime];
_identifier = [NSString stringWithFormat:@"%f/%tx", timestamp, ptr];

dispatch_barrier_async(__identifiersQueue, ^{
Expand Down
37 changes: 18 additions & 19 deletions SPDY/SPDYSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@
#import "SPDYCommonLogger.h"
#import "SPDYFrameDecoder.h"
#import "SPDYFrameEncoder.h"
#import "SPDYMetadata.h"
#import "SPDYOrigin.h"
#import "SPDYProtocol.h"
#import "SPDYSession.h"
#import "SPDYSessionManager.h"
#import "SPDYSettingsStore.h"
#import "SPDYSocket.h"
#import "SPDYStopwatch.h"
#import "SPDYStream.h"
#import "SPDYStreamManager.h"
#import "SPDYTLSTrustEvaluator.h"
#import "SPDYMetadata.h"

// The input buffer should be more than twice MAX_CHUNK_LENGTH and
// MAX_COMPRESSED_HEADER_BLOCK_LENGTH to avoid having to resize the
Expand Down Expand Up @@ -59,9 +57,8 @@ @implementation SPDYSession
NSMutableData *_inputBuffer;

SPDYStreamId _lastGoodStreamId;
CFAbsoluteTime _lastSocketActivity;
CFAbsoluteTime _sessionPingOut;
CFTimeInterval _sessionLatency;
SPDYStopwatch *_sessionPingStopwatch;
SPDYTimeInterval _sessionLatency;
NSUInteger _bufferReadIndex;
NSUInteger _bufferWriteIndex;
uint32_t _initialSendWindowSize;
Expand All @@ -78,7 +75,7 @@ @implementation SPDYSession
bool _established;
bool _receivedGoAwayFrame;
bool _sentGoAwayFrame;
CFAbsoluteTime _connectedTime;
SPDYStopwatch *_connectedStopwatch;
NSString *_connectedHost;
in_port_t _connectedPort;
}
Expand All @@ -103,6 +100,9 @@ - (id)initWithOrigin:(SPDYOrigin *)origin
return nil;
}

_sessionPingStopwatch = [[SPDYStopwatch alloc] init];
_connectedStopwatch = [[SPDYStopwatch alloc] init];

SPDYSocket *socket = [[SPDYSocket alloc] initWithDelegate:self];
bool connecting = [socket connectToHost:origin.host
onPort:origin.port
Expand Down Expand Up @@ -193,7 +193,7 @@ - (void)openStream:(SPDYStream *)stream
if (_sessionLatency >= 0) {
stream.metadata.latencyMs = (NSInteger)(_sessionLatency * 1000);
}
stream.metadata.connectedMs = (CFAbsoluteTimeGetCurrent() - _connectedTime) * 1000;
stream.metadata.connectedMs = _connectedStopwatch.elapsedSeconds * 1000;
stream.metadata.hostAddress = _connectedHost;
stream.metadata.hostPort = _connectedPort;

Expand Down Expand Up @@ -284,14 +284,12 @@ - (void)_closeWithStatus:(SPDYSessionStatus)status

- (bool)socket:(SPDYSocket *)socket securedWithTrust:(SecTrustRef)trust
{
_lastSocketActivity = CFAbsoluteTimeGetCurrent();
return [SPDYProtocol evaluateServerTrust:trust forHost:_origin.host];
}

- (void)socket:(SPDYSocket *)socket didConnectToHost:(NSString *)host port:(in_port_t)port
{
_lastSocketActivity = CFAbsoluteTimeGetCurrent();
_connectedTime = _lastSocketActivity;
[_connectedStopwatch reset];
SPDY_INFO(@"session connected to %@ (%@:%u)", _origin, host, port);

_connected = YES;
Expand All @@ -309,7 +307,6 @@ - (void)socket:(SPDYSocket *)socket didConnectToHost:(NSString *)host port:(in_p

- (void)socket:(SPDYSocket *)socket didReadData:(NSData *)data withTag:(long)tag
{
_lastSocketActivity = CFAbsoluteTimeGetCurrent();
SPDY_DEBUG(@"socket read[%li] (%lu)", tag, (unsigned long)data.length);

_bufferWriteIndex += data.length;
Expand Down Expand Up @@ -343,9 +340,8 @@ - (void)socket:(SPDYSocket *)socket didReadData:(NSData *)data withTag:(long)tag

- (void)socket:(SPDYSocket *)socket didWriteDataWithTag:(long)tag
{
_lastSocketActivity = CFAbsoluteTimeGetCurrent();
if (tag == 1) {
_sessionPingOut = _lastSocketActivity;
[_sessionPingStopwatch reset];
}
}

Expand All @@ -361,12 +357,10 @@ - (void)socket:(SPDYSocket *)socket didWriteDataWithTag:(long)tag

- (void)socket:(SPDYSocket *)socket didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag
{
_lastSocketActivity = CFAbsoluteTimeGetCurrent();
}

- (void)socket:(SPDYSocket *)socket willDisconnectWithError:(NSError *)error
{
_lastSocketActivity = CFAbsoluteTimeGetCurrent();
SPDY_WARNING(@"session connection error: %@", error);
for (SPDYStream *stream in _activeStreams) {
stream.delegate = nil;
Expand All @@ -377,7 +371,6 @@ - (void)socket:(SPDYSocket *)socket willDisconnectWithError:(NSError *)error

- (void)socketDidDisconnect:(SPDYSocket *)socket
{
_lastSocketActivity = CFAbsoluteTimeGetCurrent();
SPDY_INFO(@"session connection closed");

_connected = NO;
Expand Down Expand Up @@ -600,6 +593,12 @@ - (void)didReadSynReplyFrame:(SPDYSynReplyFrame *)synReplyFrame frameDecoder:(SP

stream.metadata.rxBytes += synReplyFrame.encodedLength;

// While we prefer to record latencyMs at stream open time, the ping response may not have been
// received yet. It will have been received by this point however.
if (_sessionLatency >= 0 && stream.metadata.latencyMs <= 0) {
stream.metadata.latencyMs = (NSInteger)(_sessionLatency * 1000);
}

// Check if we have received multiple frames for the same Stream-ID
if (stream.receivedReply) {
SPDY_WARNING(@"received duplicate SYN_REPLY");
Expand Down Expand Up @@ -722,7 +721,7 @@ - (void)didReadPingFrame:(SPDYPingFrame *)pingFrame frameDecoder:(SPDYFrameDecod

if (pingId & 1) {
if (pingId == 1) {
_sessionLatency = CFAbsoluteTimeGetCurrent() - _sessionPingOut;
_sessionLatency = _sessionPingStopwatch.elapsedSeconds;
SPDY_DEBUG(@"received PING.%u response (%f)", pingId, _sessionLatency);
_established = YES;
}
Expand Down
20 changes: 20 additions & 0 deletions SPDY/SPDYStopwatch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// SPDYStopwatch.h
// SPDY
//
// Copyright (c) 2014 Twitter, Inc. All rights reserved.
// Licensed under the Apache License v2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Created by Kevin Goodier.
//

typedef double SPDYTimeInterval;

@interface SPDYStopwatch : NSObject
+ (SPDYTimeInterval)currentSystemTime;
+ (SPDYTimeInterval)currentAbsoluteTime;
- (id)init;
- (void)reset;
- (SPDYTimeInterval)elapsedSeconds;
@end
67 changes: 67 additions & 0 deletions SPDY/SPDYStopwatch.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// SPDYStopwatch.m
// SPDY
//
// Copyright (c) 2014 Twitter, Inc. All rights reserved.
// Licensed under the Apache License v2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Created by Kevin Goodier
//

#import <mach/mach_time.h>
#import "SPDYStopwatch.h"

@implementation SPDYStopwatch
{
SPDYTimeInterval _startTime;
}

static dispatch_once_t __initTimebase;
static double __machTimebaseToSeconds;
static mach_timebase_info_data_t __machTimebase;

+ (void)initialize
{
dispatch_once(&__initTimebase, ^{
kern_return_t status = mach_timebase_info(&__machTimebase);
// Everything will be 0 if this fails.
if (status != KERN_SUCCESS) {
__machTimebase.numer = 0;
__machTimebase.denom = 1;
}
__machTimebaseToSeconds = (double)__machTimebase.numer / ((double)__machTimebase.denom * 1000000000.0);
});
}

+ (SPDYTimeInterval)currentSystemTime
{
uint64_t now = mach_absolute_time();
return (SPDYTimeInterval)now * __machTimebaseToSeconds;
}

+ (SPDYTimeInterval)currentAbsoluteTime
{
return CFAbsoluteTimeGetCurrent();
}

- (id)init
{
self = [super init];
if (self) {
_startTime = [SPDYStopwatch currentAbsoluteTime];
}
return self;
}

- (void)reset
{
_startTime = [SPDYStopwatch currentAbsoluteTime];
}

- (SPDYTimeInterval)elapsedSeconds
{
return [SPDYStopwatch currentAbsoluteTime] - _startTime;
}

@end