Chrome デベロッパーツールに表示される Priority と HTTP/2

このエントリは、 http2 advent calendar の 5 日目です。


ここまで豪華執筆陣による記事が続いてるところに恐縮です。
(あとChromeのコードへの理解が足りず、間違いがあるかもしれません。ご注意下さい)

Chrome デベロッパーツールのPriority

先日、Chrome 安定版のバージョン47が公開されました。


このバージョンより、デベロッパーツールのネットワークパネルにおいて、Priorityという項目が追加されました(右クリックで追加可能)。

https://asnokaze.com/test.html


この Priority とは何なのかについて少々書いてみようと思います。
だらだら書いていくので、結論が知りたい場合は一番下のまとめまで飛ばして下さい。

ResourceLoadPriority

定義

先述のデベロッパーツールのPriorityの項目は、HTTP/1.1の通信においても優先度付されているので直接的にHTTP/2のPriorityを指し示してない事はわかるかと思います。


ここで表示されいてるPriorityは単純にChromeの中での優先度にすぎません。この優先度の定義はBlinkのコード見てみると分かりやすいかと思います。

enum ResourceLoadPriority {
    // The unresolved priority is here for the convenience of the clients. It
    // should not be passed to the ResourceLoadScheduler.
    ResourceLoadPriorityUnresolved = -1,
    ResourceLoadPriorityVeryLow = 0,
    ResourceLoadPriorityLow,
    ResourceLoadPriorityMedium,
    ResourceLoadPriorityHigh,
    ResourceLoadPriorityVeryHigh,
    ResourceLoadPriorityLowest = ResourceLoadPriorityVeryLow,
    ResourceLoadPriorityHighest = ResourceLoadPriorityVeryHigh,
};

https://chromium.googlesource.com/chromium/blink/+/master/Source/platform/network/ResourceLoadPriority.h


コードの定義の通り「Lowest, Low, Medium, High, Highest」の5段階であることが確認できます。


Priority付け

次に各リクエストがどのように優先度付けされるか確認します。同じくBlinkのコードを見てみます。
typeToPriorityメソッドにおいて、Resource::Type毎のデフォルトのPriorityが決められます。

static ResourceLoadPriority typeToPriority(Resource::Type type)
{
    switch (type) {
    case Resource::MainResource:
        return ResourceLoadPriorityVeryHigh;
    case Resource::XSLStyleSheet:
        ASSERT(RuntimeEnabledFeatures::xsltEnabled());
    case Resource::CSSStyleSheet:
        return ResourceLoadPriorityHigh;
    case Resource::Raw:
    case Resource::Script:
    case Resource::Font:
    case Resource::ImportResource:
        return ResourceLoadPriorityMedium;
    case Resource::LinkSubresource:
    case Resource::TextTrack:
    case Resource::Media:
    case Resource::SVGDocument:
        return ResourceLoadPriorityLow;
    case Resource::Image:
    case Resource::LinkPrefetch:
    case Resource::LinkPreload:
        return ResourceLoadPriorityVeryLow;
    }
    ASSERT_NOT_REACHED();
    return ResourceLoadPriorityUnresolved;
}

https://chromium.googlesource.com/chromium/blink/+/master/Source/core/fetch/ResourceFetcher.cpp#60


以下の様なことが分かります

  • MainResource(Document)はVeriHigh
  • CSSはHigh
  • ScriptはMedium


合わせて以下の部分も確認します。
modifyPriorityForExperimentsメソッドにて、Priorityが変更されます。

ResourceLoadPriority FrameFetchContext::modifyPriorityForExperiments(ResourceLoadPriority priority, Resource::Type type, const FetchRequest& request)
//中略
    // If enabled, drop the priority of all resources in a subframe.
    if (frame()->settings()->lowPriorityIframes() && !frame()->isMainFrame())
        return ResourceLoadPriorityVeryLow;

    // Async/Defer scripts.
    if (type == Resource::Script && FetchRequest::LazyLoad == request.defer())
        return frame()->settings()->fetchIncreaseAsyncScriptPriority() ? ResourceLoadPriorityMedium : ResourceLoadPriorityLow;

https://chromium.googlesource.com/chromium/blink/+/master/Source/core/loader/FrameFetchContext.cpp#684


例えばAsync属性の付いたScriptはPriorityがLowに変更されていることが分かります。

再確認

改めてデベロッパーツールを確認します


Type毎に指定されたPriorityになっていることが確認できます。また、ScriptでもPriorityが違うのは、Asyncを指定していたのが理由だと分かりました。


spdy_priority

さて、続いては ResourceLoadPriority と HTTP/2のPriorityの関係を見ていきます。
注意点としては、ChromeではSPDY/4 == HTTP/2として扱っています。そのため、コード上でspdyと出てきたからと言ってSPDY/2, SPDY3の事を指してるとは限らない点に注意です。


定義としても、SPDY version 4 == HTTP2 となっていることが確認できます。

enum SpdyMajorVersion {
  SPDY2 = 2,
  SPDY_MIN_VERSION = SPDY2,
  SPDY3 = 3,
  HTTP2 = 4,
  SPDY_MAX_VERSION = HTTP2
};

https://code.google.com/p/chromium/codesearch#chromium/src/net/spdy/spdy_protocol.h&l=35

HEADERSフレームの生成

では、Chromeの中でHEADERSフレームに優先度を設定している部分を確認します。

scoped_ptr<SpdyFrame> SpdySession::CreateSynStream(
    SpdyStreamId stream_id,
    RequestPriority priority,
    SpdyControlFlags flags,
    const SpdyHeaderBlock& block) {

  //(中略)
  SpdyPriority spdy_priority =
      ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion());

  if (GetProtocolVersion() <= SPDY3) {
  //(中略)
  } else {
    SpdyHeadersIR headers(stream_id);
    headers.set_priority(spdy_priority);
    headers.set_has_priority(true);

https://code.google.com/p/chromium/codesearch#chromium/src/net/spdy/spdy_session.cc&l=1074


CreateSynStreamメソッドですが、HTTP/2のheadersフレームの生成も行っています。
Priorityの設定のキモは、ConvertRequestPriorityToSpdyPriorityメソッドで、リクエストのPriority(多分、最初に説明したResourceLoadPriority)をHTTP/2の世界のSpdyPriorityに変換しているところです。

Weight計算

御存知の通り、HTTP/2ではdependency based priorityという、依存関係と重みを用いた優先度付が行われます。しかし、現在Chromeでは、依存関係を使用しておらず、Weightのみの優先度付を行っています。


そのWeightがどのように決まるかは、ConvertRequestPriorityToSpdyPrioritの実装とあわせて確認していきます。

SpdyPriority ConvertRequestPriorityToSpdyPriority(
    const RequestPriority priority,
    SpdyMajorVersion protocol_version) {
  DCHECK_GE(priority, MINIMUM_PRIORITY);
  DCHECK_LE(priority, MAXIMUM_PRIORITY);
  return static_cast<SpdyPriority>(MAXIMUM_PRIORITY - priority);
}

https://code.google.com/p/chromium/codesearch#chromium/src/net/spdy/spdy_http_utils.cc&l=191

uint8 SpdyFramer::MapPriorityToWeight(SpdyPriority priority) {
  const float kSteps = 255.9f / 7.f;
  return static_cast<uint8>(kSteps * (7.f - priority));
}

HTTP/2のWeightの最大値 256を7分割し、SpdyPriorityが係数として掛けられる形になる。

答え合わせ

サーバ側でHEADERSフレームのWeightを見てみると、見てきたとおりのWeightになっていることが確認できました。

request line: "GET /test.html HTTP/2.0"  weight:256
request line: "GET /test.js?1 HTTP/2.0"  weight:183
request line: "GET /test.css?1 HTTP/2.0" weight:220
request line: "GET /test.js?2 HTTP/2.0"  weight:147
request line: "GET /test.css?2 HTTP/2.0" weight:220
request line: "GET /test.css?3 HTTP/2.0" weight:220
request line: "GET /test.js?3 HTTP/2.0"  weight:183
request line: "GET /a.gif HTTP/2.0"      weight:110

まとめ

つまり、TypeとResourceLoadPriorityとspdy_priority(値)とWeightの関係は以下のようになる。


dependencyないのにLowestでもWeight:100は全然Lowestじゃないのでは?)

余談

Chromeでもdependencyに関する実装が進められている模様
https://codereview.chromium.org/1411383005/


夏風先生の資料が非常に分かりやすいです。
https://speakerdeck.com/summerwind/2-prioritization