NetworkMarker.cpp (18737B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set expandtab ts=4 sw=2 sts=2 cin: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "NetworkMarker.h" 8 9 #include "HttpBaseChannel.h" 10 #include "nsIChannelEventSink.h" 11 #include "mozilla/Perfetto.h" 12 #include "mozilla/ErrorNames.h" 13 #include "nsHttpHandler.h" 14 #include "nsIClassOfService.h" 15 16 namespace mozilla::net { 17 struct NetworkMarker { 18 static constexpr Span<const char> MarkerTypeName() { 19 return MakeStringSpan("Network"); 20 } 21 static void StreamJSONMarkerData( 22 baseprofiler::SpliceableJSONWriter& aWriter, mozilla::TimeStamp aStart, 23 mozilla::TimeStamp aEnd, int64_t aID, const ProfilerString8View& aURI, 24 const ProfilerString8View& aRequestMethod, NetworkLoadType aType, 25 int32_t aPri, int64_t aCount, 26 nsICacheInfoChannel::CacheDisposition aCacheDisposition, 27 bool aIsPrivateBrowsing, const net::TimingStruct& aTimings, 28 const ProfilerString8View& aRedirectURI, 29 const ProfilerString8View& aContentType, uint32_t aRedirectFlags, 30 int64_t aRedirectChannelId, uint32_t aClassOfServiceFlags, 31 bool aClassOfServiceIncremental, nsresult aRequestStatus, 32 const mozilla::Maybe<mozilla::net::HttpVersion> aHttpVersion, 33 mozilla::Maybe<uint32_t> aResponseStatus) { 34 // This payload still streams a startTime and endTime property because it 35 // made the migration to MarkerTiming on the front-end easier. 36 aWriter.TimeProperty("startTime", aStart); 37 aWriter.TimeProperty("endTime", aEnd); 38 39 aWriter.IntProperty("id", aID); 40 aWriter.StringProperty("status", GetNetworkState(aType)); 41 42 // Bug 1919148 - Moved aClassOfServiceStr here to ensure that we call 43 // aWriter.StringProperty before the lifetime of nsAutoCString ends 44 nsAutoCString aClassOfServiceStr; 45 GetClassOfService(aClassOfServiceStr, aClassOfServiceFlags); 46 MOZ_ASSERT(aClassOfServiceStr.Length() > 0, 47 "aClassOfServiceStr should be set after GetClassOfService"); 48 aWriter.StringProperty("classOfService", 49 MakeStringSpan(aClassOfServiceStr.get())); 50 51 uint8_t urgency = 52 nsHttpHandler::UrgencyFromCoSFlags(aClassOfServiceFlags, aPri); 53 nsAutoCString priorityHeader; 54 priorityHeader.AppendPrintf("u=%d", urgency); 55 if (aClassOfServiceIncremental) { 56 priorityHeader.Append(", i"); 57 } 58 aWriter.StringProperty("priorityHeader", 59 MakeStringSpan(priorityHeader.get())); 60 61 nsAutoCString aRequestStatusStr; 62 GetErrorName(aRequestStatus, aRequestStatusStr); 63 aWriter.StringProperty("requestStatus", 64 MakeStringSpan(aRequestStatusStr.get())); 65 66 if (Span<const char> cacheString = GetCacheState(aCacheDisposition); 67 !cacheString.IsEmpty()) { 68 aWriter.StringProperty("cache", cacheString); 69 } 70 aWriter.IntProperty("pri", aPri); 71 if (aCount > 0) { 72 aWriter.IntProperty("count", aCount); 73 } 74 if (aURI.Length() != 0) { 75 aWriter.StringProperty("URI", aURI); 76 } 77 if (aRedirectURI.Length() != 0) { 78 aWriter.StringProperty("RedirectURI", aRedirectURI); 79 aWriter.StringProperty("redirectType", getRedirectType(aRedirectFlags)); 80 aWriter.BoolProperty( 81 "isHttpToHttpsRedirect", 82 aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE); 83 84 if (aRedirectChannelId != 0) { 85 aWriter.IntProperty("redirectId", aRedirectChannelId); 86 } 87 } 88 89 aWriter.StringProperty("requestMethod", aRequestMethod); 90 if (aHttpVersion) { 91 aWriter.StringProperty("httpVersion", 92 ProfilerString8View::WrapNullTerminatedString( 93 nsHttp::GetProtocolVersion(*aHttpVersion))); 94 } 95 if (aResponseStatus) { 96 aWriter.IntProperty("responseStatus", *aResponseStatus); 97 } 98 99 if (aContentType.Length() != 0) { 100 aWriter.StringProperty("contentType", aContentType); 101 } else { 102 aWriter.NullProperty("contentType"); 103 } 104 105 if (aIsPrivateBrowsing) { 106 aWriter.BoolProperty("isPrivateBrowsing", aIsPrivateBrowsing); 107 } 108 109 if (aType != NetworkLoadType::LOAD_START) { 110 aWriter.TimeProperty("domainLookupStart", aTimings.domainLookupStart); 111 aWriter.TimeProperty("domainLookupEnd", aTimings.domainLookupEnd); 112 aWriter.TimeProperty("connectStart", aTimings.connectStart); 113 aWriter.TimeProperty("tcpConnectEnd", aTimings.tcpConnectEnd); 114 aWriter.TimeProperty("secureConnectionStart", 115 aTimings.secureConnectionStart); 116 aWriter.TimeProperty("connectEnd", aTimings.connectEnd); 117 aWriter.TimeProperty("requestStart", aTimings.requestStart); 118 aWriter.TimeProperty("responseStart", aTimings.responseStart); 119 aWriter.TimeProperty("responseEnd", aTimings.responseEnd); 120 } 121 } 122 static MarkerSchema MarkerTypeDisplay() { 123 return MarkerSchema::SpecialFrontendLocation{}; 124 } 125 126 static Span<const char> GetNetworkState(NetworkLoadType aType) { 127 switch (aType) { 128 case NetworkLoadType::LOAD_START: 129 return MakeStringSpan("STATUS_START"); 130 case NetworkLoadType::LOAD_STOP: 131 return MakeStringSpan("STATUS_STOP"); 132 case NetworkLoadType::LOAD_REDIRECT: 133 return MakeStringSpan("STATUS_REDIRECT"); 134 case NetworkLoadType::LOAD_CANCEL: 135 return MakeStringSpan("STATUS_CANCEL"); 136 default: 137 MOZ_ASSERT(false, "Unexpected NetworkLoadType enum value."); 138 return MakeStringSpan(""); 139 } 140 } 141 142 static Span<const char> GetCacheState( 143 nsICacheInfoChannel::CacheDisposition aCacheDisposition) { 144 switch (aCacheDisposition) { 145 case nsICacheInfoChannel::kCacheUnresolved: 146 return MakeStringSpan("Unresolved"); 147 case nsICacheInfoChannel::kCacheHit: 148 return MakeStringSpan("Hit"); 149 case nsICacheInfoChannel::kCacheHitViaReval: 150 return MakeStringSpan("HitViaReval"); 151 case nsICacheInfoChannel::kCacheMissedViaReval: 152 return MakeStringSpan("MissedViaReval"); 153 case nsICacheInfoChannel::kCacheMissed: 154 return MakeStringSpan("Missed"); 155 case nsICacheInfoChannel::kCacheUnknown: 156 return MakeStringSpan(""); 157 default: 158 MOZ_ASSERT(false, "Unexpected CacheDisposition enum value."); 159 return MakeStringSpan(""); 160 } 161 } 162 163 static Span<const char> getRedirectType(uint32_t aRedirectFlags) { 164 MOZ_ASSERT(aRedirectFlags != 0, "aRedirectFlags should be non-zero"); 165 if (aRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) { 166 return MakeStringSpan("Temporary"); 167 } 168 if (aRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) { 169 return MakeStringSpan("Permanent"); 170 } 171 if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { 172 return MakeStringSpan("Internal"); 173 } 174 MOZ_ASSERT(false, "Couldn't find a redirect type from aRedirectFlags"); 175 return MakeStringSpan(""); 176 } 177 178 // Update an empty string aClassOfServiceStr based on aClassOfServiceFlag 179 static void GetClassOfService(nsAutoCString& aClassOfServiceStr, 180 uint32_t aClassOfServiceFlag) { 181 MOZ_ASSERT(aClassOfServiceStr.IsEmpty(), 182 "Flags should not be appended to aClassOfServiceStr before " 183 "calling GetClassOfService"); 184 185 if (aClassOfServiceFlag & nsIClassOfService::Leader) { 186 aClassOfServiceStr.Append("Leader | "); 187 } 188 if (aClassOfServiceFlag & nsIClassOfService::Follower) { 189 aClassOfServiceStr.Append("Follower | "); 190 } 191 if (aClassOfServiceFlag & nsIClassOfService::Speculative) { 192 aClassOfServiceStr.Append("Speculative | "); 193 } 194 if (aClassOfServiceFlag & nsIClassOfService::Background) { 195 aClassOfServiceStr.Append("Background | "); 196 } 197 if (aClassOfServiceFlag & nsIClassOfService::Unblocked) { 198 aClassOfServiceStr.Append("Unblocked | "); 199 } 200 if (aClassOfServiceFlag & nsIClassOfService::Throttleable) { 201 aClassOfServiceStr.Append("Throttleable | "); 202 } 203 if (aClassOfServiceFlag & nsIClassOfService::UrgentStart) { 204 aClassOfServiceStr.Append("UrgentStart | "); 205 } 206 if (aClassOfServiceFlag & nsIClassOfService::DontThrottle) { 207 aClassOfServiceStr.Append("DontThrottle | "); 208 } 209 if (aClassOfServiceFlag & nsIClassOfService::Tail) { 210 aClassOfServiceStr.Append("Tail | "); 211 } 212 if (aClassOfServiceFlag & nsIClassOfService::TailAllowed) { 213 aClassOfServiceStr.Append("TailAllowed | "); 214 } 215 if (aClassOfServiceFlag & nsIClassOfService::TailForbidden) { 216 aClassOfServiceStr.Append("TailForbidden | "); 217 } 218 219 if (aClassOfServiceStr.IsEmpty()) { 220 aClassOfServiceStr.Append("Unset"); 221 return; 222 } 223 224 MOZ_ASSERT(aClassOfServiceStr.Length() > 3, 225 "aClassOfServiceStr must be at least 4 characters long to " 226 "include two blank spaces and a '|' character."); 227 // Remove the trailing '|' 228 aClassOfServiceStr.Truncate(aClassOfServiceStr.Length() - 3); 229 } 230 }; 231 } // namespace mozilla::net 232 233 #ifdef MOZ_PERFETTO 234 // Define a specialization for NetworkMarker since the payloads are 235 // not trivial to translate directly. 236 template <> 237 void EmitPerfettoTrackEvent<mozilla::net::NetworkMarker, mozilla::TimeStamp, 238 mozilla::TimeStamp, int64_t, nsAutoCStringN<2048>, 239 nsACString, mozilla::net::NetworkLoadType, int32_t, 240 int64_t, nsICacheInfoChannel::CacheDisposition, 241 bool, mozilla::net::TimingStruct, nsAutoCString, 242 mozilla::ProfilerString8View, uint32_t, uint64_t>( 243 const mozilla::ProfilerString8View& aName, 244 const mozilla::MarkerCategory& aCategory, 245 const mozilla::MarkerOptions& aOptions, 246 mozilla::net::NetworkMarker aMarkerType, const mozilla::TimeStamp& aStart, 247 const mozilla::TimeStamp& aEnd, const int64_t& aID, 248 const nsAutoCStringN<2048>& aURI, const nsACString& aRequestMethod, 249 const mozilla::net::NetworkLoadType& aType, const int32_t& aPri, 250 const int64_t& aCount, 251 const nsICacheInfoChannel::CacheDisposition& aCacheDisposition, 252 const bool& aIsPrivateBrowsing, const mozilla::net::TimingStruct& aTimings, 253 const nsAutoCString& aRedirectURI, 254 const mozilla::ProfilerString8View& aContentType, 255 const uint32_t& aRedirectFlags, const uint64_t& aRedirectChannelId) { 256 MOZ_ASSERT(!aOptions.IsTimingUnspecified(), 257 "Timing should be properly defined."); 258 const char* nameStr = aName.StringView().data(); 259 if (!nameStr) { 260 return; 261 } 262 263 mozilla::TimeStamp startTime, endTime; 264 startTime = aOptions.Timing().StartTime(); 265 endTime = aOptions.Timing().EndTime(); 266 267 perfetto::DynamicString name{nameStr}; 268 perfetto::DynamicCategory category{"LOAD"}; 269 270 MOZ_ASSERT( 271 aOptions.Timing().MarkerPhase() == mozilla::MarkerTiming::Phase::Interval, 272 "Expecting an interval phase only."); 273 274 // Create a unique id for each marker. 275 mozilla::HashNumber hash = 276 mozilla::HashStringKnownLength(nameStr, aName.StringView().length()); 277 hash = mozilla::AddToHash(hash, 278 startTime.RawClockMonotonicNanosecondsSinceBoot()); 279 hash = 280 mozilla::AddToHash(hash, endTime.RawClockMonotonicNanosecondsSinceBoot()); 281 perfetto::Track track(hash); 282 283 auto desc = track.Serialize(); 284 desc.set_name(nameStr); 285 perfetto::TrackEvent::SetTrackDescriptor(track, desc); 286 287 PERFETTO_TRACE_EVENT_BEGIN(category, name, track, startTime); 288 PERFETTO_TRACE_EVENT_END( 289 category, track, endTime, [&](perfetto::EventContext ctx) { 290 auto* urlArg = ctx.event()->add_debug_annotations(); 291 urlArg->set_name("url"); 292 urlArg->set_string_value(aURI.get()); 293 294 auto* reqMethodArg = ctx.event()->add_debug_annotations(); 295 reqMethodArg->set_name("requestMethod"); 296 reqMethodArg->set_string_value(nsAutoCString(aRequestMethod).get()); 297 298 auto* statusArg = ctx.event()->add_debug_annotations(); 299 statusArg->set_name("status"); 300 statusArg->set_string_value( 301 mozilla::net::NetworkMarker::GetNetworkState(aType).data()); 302 303 auto* cacheArg = ctx.event()->add_debug_annotations(); 304 cacheArg->set_name("cache"); 305 cacheArg->set_string_value( 306 mozilla::net::NetworkMarker::GetCacheState(aCacheDisposition) 307 .data()); 308 309 if (aContentType.Length() != 0) { 310 auto* contentTypeArg = ctx.event()->add_debug_annotations(); 311 contentTypeArg->set_name("contentType"); 312 contentTypeArg->set_string_value(aContentType.StringView().data()); 313 } 314 315 auto* priorityArg = ctx.event()->add_debug_annotations(); 316 priorityArg->set_name("priority"); 317 priorityArg->set_int_value(aPri); 318 319 if (aCount > 0) { 320 auto* countArg = ctx.event()->add_debug_annotations(); 321 countArg->set_name("count"); 322 countArg->set_int_value(aCount); 323 } 324 325 if (aRedirectURI.Length() != 0) { 326 auto* redirectURIArg = ctx.event()->add_debug_annotations(); 327 redirectURIArg->set_name("RedirectURI"); 328 redirectURIArg->set_string_value(aRedirectURI.get()); 329 330 auto* redirectTypeArg = ctx.event()->add_debug_annotations(); 331 redirectTypeArg->set_name("redirectType"); 332 redirectTypeArg->set_string_value( 333 mozilla::net::NetworkMarker::getRedirectType(aRedirectFlags) 334 .data()); 335 336 auto* httpToHttpsArg = ctx.event()->add_debug_annotations(); 337 httpToHttpsArg->set_name("isHttpToHttpsRedirect"); 338 httpToHttpsArg->set_bool_value( 339 aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE); 340 341 if (aRedirectChannelId != 0) { 342 auto* redirectIdArg = ctx.event()->add_debug_annotations(); 343 redirectIdArg->set_name("redirectId"); 344 redirectIdArg->set_int_value(aRedirectChannelId); 345 } 346 } 347 348 if (aIsPrivateBrowsing) { 349 auto* privateBrowsingArg = ctx.event()->add_debug_annotations(); 350 privateBrowsingArg->set_name("isPrivateBrowsing"); 351 privateBrowsingArg->set_bool_value(aIsPrivateBrowsing); 352 } 353 354 if (aType != mozilla::net::NetworkLoadType::LOAD_START) { 355 mozilla::TimeStamp startTime; 356 auto addNetworkTimingAnnotation = 357 [&startTime, &ctx, &aStart](const mozilla::TimeStamp& endTime, 358 const char* name) { 359 if (endTime) { 360 // If startTime is not defined, redefine the name of this to 361 // "Waiting for Socket Thread". 362 if (!startTime) { 363 name = "Waiting for Socket Thread (us)"; 364 startTime = aStart; 365 } 366 mozilla::TimeDuration duration = endTime - startTime; 367 auto* arg = ctx.event()->add_debug_annotations(); 368 arg->set_name(name); 369 arg->set_int_value(duration.ToMilliseconds()); 370 startTime = endTime; 371 } 372 }; 373 374 addNetworkTimingAnnotation(aTimings.domainLookupStart, 375 "Waiting for Socket Thread"); 376 addNetworkTimingAnnotation(aTimings.domainLookupEnd, "DNS Request"); 377 addNetworkTimingAnnotation(aTimings.connectStart, 378 "After DNS Request"); 379 addNetworkTimingAnnotation(aTimings.tcpConnectEnd, "TCP connection"); 380 addNetworkTimingAnnotation(aTimings.secureConnectionStart, 381 "After TCP connection"); 382 addNetworkTimingAnnotation(aTimings.connectEnd, 383 "Establishing TLS session"); 384 addNetworkTimingAnnotation(aTimings.requestStart, 385 "Waiting for HTTP request"); 386 addNetworkTimingAnnotation(aTimings.responseStart, 387 "HTTP request and waiting for response"); 388 addNetworkTimingAnnotation(aTimings.responseEnd, "HTTP response"); 389 addNetworkTimingAnnotation(aEnd, "Waiting to transmit the response"); 390 } 391 }); 392 } 393 #endif // MOZ_PERFETTO 394 395 namespace mozilla::net { 396 static constexpr net::TimingStruct scEmptyNetTimingStruct; 397 398 void profiler_add_network_marker( 399 nsIURI* aURI, const nsACString& aRequestMethod, int32_t aPriority, 400 uint64_t aChannelId, NetworkLoadType aType, mozilla::TimeStamp aStart, 401 mozilla::TimeStamp aEnd, int64_t aCount, 402 nsICacheInfoChannel::CacheDisposition aCacheDisposition, 403 uint64_t aInnerWindowID, bool aIsPrivateBrowsing, 404 nsIClassOfService* aClassOfService, nsresult aRequestStatus, 405 const mozilla::net::TimingStruct* aTimings, 406 UniquePtr<ProfileChunkedBuffer> aSource, 407 const Maybe<mozilla::net::HttpVersion> aHttpVersion, 408 const Maybe<uint32_t> aResponseStatus, 409 const Maybe<nsDependentCString>& aContentType, nsIURI* aRedirectURI, 410 uint32_t aRedirectFlags, uint64_t aRedirectChannelId) { 411 if (!profiler_thread_is_being_profiled_for_markers()) { 412 return; 413 } 414 415 nsAutoCStringN<2048> name; 416 name.AppendASCII("Load "); 417 // top 32 bits are process id of the load 418 name.AppendInt(aChannelId & 0xFFFFFFFFu); 419 420 // These can do allocations/frees/etc; avoid if not active 421 nsAutoCStringN<2048> spec; 422 if (aURI) { 423 aURI->GetAsciiSpec(spec); 424 name.AppendASCII(": "); 425 name.Append(spec); 426 } 427 428 nsAutoCString redirect_spec; 429 if (aRedirectURI) { 430 aRedirectURI->GetAsciiSpec(redirect_spec); 431 } 432 433 uint32_t classOfServiceFlags = 0; 434 bool classOfServiceIncremental = false; 435 if (aClassOfService) { 436 aClassOfService->GetClassFlags(&classOfServiceFlags); 437 aClassOfService->GetIncremental(&classOfServiceIncremental); 438 } 439 440 profiler_add_marker( 441 name, geckoprofiler::category::NETWORK, 442 {MarkerTiming::Interval(aStart, aEnd), 443 MarkerStack::TakeBacktrace(std::move(aSource)), 444 MarkerInnerWindowId(aInnerWindowID)}, 445 NetworkMarker{}, aStart, aEnd, static_cast<int64_t>(aChannelId), spec, 446 aRequestMethod, aType, aPriority, aCount, aCacheDisposition, 447 aIsPrivateBrowsing, aTimings ? *aTimings : scEmptyNetTimingStruct, 448 redirect_spec, 449 aContentType ? ProfilerString8View(*aContentType) : ProfilerString8View(), 450 aRedirectFlags, aRedirectChannelId, classOfServiceFlags, 451 classOfServiceIncremental, aRequestStatus, aHttpVersion, aResponseStatus); 452 } 453 } // namespace mozilla::net