วิธีที่เราเร่งสแต็กเทรซ Chrome DevTools ได้มากถึง 10 เท่า

เบเนดิคต์ เมอร์เรอร์
Benedikt Meurer

นักพัฒนาเว็บคาดว่าจะมีผลกระทบด้านประสิทธิภาพเพียงเล็กน้อยหรือไม่มีเลยเมื่อแก้ไขข้อบกพร่องของโค้ด อย่างไรก็ตาม ความคาดหวังนี้ยังไม่เป็นสากล นักพัฒนาซอฟต์แวร์ C++ ไม่คาดหวังว่ารุ่นการแก้ไขข้อบกพร่องของแอปพลิเคชันของตนจะได้ประสิทธิภาพในเวอร์ชันที่ใช้งานจริง และในช่วงปีแรกๆ ที่ Chrome เปิดตัว แค่เปิด DevTools ก็ส่งผลต่อประสิทธิภาพของหน้าอย่างมาก

เราไม่รู้สึกว่าประสิทธิภาพที่ลดลงนี้เป็นผลมาจากการลงทุนด้านความสามารถในการแก้ไขข้อบกพร่องของ DevTools และ V8 เป็นเวลาหลายปีอีกต่อไป อย่างไรก็ตาม เราไม่สามารถลดภาระการทำงานของเครื่องมือสำหรับนักพัฒนาเว็บให้เป็น 0 ได้ การตั้งค่าเบรกพอยท์ การก้าวผ่านโค้ด การรวบรวมสแต็กเทรซ การบันทึกการติดตามประสิทธิภาพ และอื่นๆ ทั้งหมดจะส่งผลต่อความเร็วในการดำเนินการในระดับที่ต่างกัน ทั้งนี้ก็เพราะการสังเกตบางอย่างจะเปลี่ยนแปลง

แต่แน่นอนว่าค่าใช้จ่ายสำหรับ DevTools อย่างโปรแกรมแก้ไขข้อบกพร่องต่างๆ ก็น่าจะสมเหตุสมผล เมื่อเร็วๆ นี้เราพบว่ามีการรายงานเพิ่มขึ้นอย่างมากซึ่งในบางกรณี เครื่องมือสำหรับนักพัฒนาเว็บจะชะลอแอปพลิเคชันจนไม่สามารถใช้งานได้อีก คุณสามารถดูการเปรียบเทียบเทียบเคียงกันได้จากรายงาน chromium:1069425 ซึ่งจะแสดงประสิทธิภาพของส่วนเกินของการเปิดเครื่องมือสำหรับนักพัฒนาเว็บเท่านั้น

คุณจะเห็นได้จากวิดีโอว่าความเร็วช้าลงตามลำดับที่ 5-10 เท่า ซึ่งเป็นเรื่องที่เรายอมรับไม่ได้ ขั้นตอนแรกคือการทำความเข้าใจจุดที่ต้องปรับปรุงและสาเหตุของชะลอตัวครั้งใหญ่นี้เมื่อเปิดเครื่องมือสำหรับนักพัฒนาเว็บ การใช้ Linux Perf ในกระบวนการแสดงผล Chrome แสดงให้เห็นการกระจายเวลาดำเนินการกับโหมดแสดงภาพโดยรวมดังต่อไปนี้

เวลาดำเนินการของ Chrome Renderer

แม้เราจะค่อนข้างคาดหวังว่าจะเห็นสิ่งที่เกี่ยวข้องกับการรวบรวมสแต็กเทรซ แต่เราก็ไม่ได้คาดหวังว่าเวลาประมาณ 90% ของเวลาการดำเนินการโดยรวมจะเป็นสัญลักษณ์ของสแต็กเฟรม การสร้างสัญลักษณ์ในที่นี้หมายถึงการดำเนินการแปลชื่อฟังก์ชันและตำแหน่งแหล่งที่มาที่เป็นรูปธรรม ซึ่งได้แก่ หมายเลขบรรทัดและคอลัมน์ในสคริปต์ จากสแต็กเฟรมดิบ

การอนุมานชื่อเมธอด

สิ่งที่น่าประหลาดใจไปกว่านั้นก็คือการที่เกือบทุกครั้งต้องใช้ฟังก์ชัน JSStackFrame::GetMethodName() ใน V8 แม้ว่าเราจะทราบจากการตรวจสอบก่อนหน้านี้ว่า JSStackFrame::GetMethodName() ไม่ใช่คนแปลกหน้าที่มีปัญหาด้านประสิทธิภาพ ฟังก์ชันนี้พยายามคำนวณชื่อของเมธอดสำหรับเฟรมที่ถือว่าเป็นการเรียกใช้เมธอด (เฟรมที่แสดงการเรียกใช้ฟังก์ชันของรูปแบบ obj.func() แทนที่จะเป็น func()) เมื่อมองเข้าไปในโค้ดอย่างรวดเร็วทำให้รู้ว่าโค้ดทำงานโดยการดำเนินการส่งผ่านผ่านวัตถุและเชนต้นแบบของวัตถุนั้นโดยสมบูรณ์ จากนั้นมองหาโค้ด

  1. พร็อพเพอร์ตี้ข้อมูลที่ value มีการปิด func หรือ
  2. พร็อพเพอร์ตี้ตัวเข้าถึงที่ get หรือ set เท่ากับการปิด func

แม้ว่าตอนนี้จะดูเหมือนไม่ได้ราคาถูกเท่าไหร่ แต่ฟังดูไม่ใช่สิ่งที่อธิบายภาวะชะลอตัวที่น่ากลัวขนาดนี้ เราจึงเริ่มค้นหาข้อมูลในตัวอย่างที่รายงานใน chromium:1069425 แล้วก็พบว่ามีการรวบรวมสแต็กเทรซสำหรับงานที่ไม่พร้อมกัน รวมถึงข้อความบันทึกที่มาจาก classes.js ซึ่งเป็นไฟล์ JavaScript ขนาด 10 MiB เมื่อมองให้ชัดขึ้น จะเห็นว่าโดยพื้นฐานแล้วนี่เป็นรันไทม์ของ Java บวกกับโค้ดของแอปพลิเคชันที่คอมไพล์เป็น JavaScript สแต็กเทรซมีเฟรมหลายเฟรมที่มีเมธอดเรียกใช้ในวัตถุ A เราจึงคิดว่าคุณควรทำความเข้าใจเกี่ยวกับออบเจ็กต์ประเภทใด

สแต็กเทรซของออบเจ็กต์

ดูเหมือนว่าคอมไพเลอร์ Java เป็น JavaScript ได้สร้างออบเจ็กต์เดี่ยวด้วยฟังก์ชัน 82,203 อันมหาศาล เห็นได้ชัดว่าเริ่มน่าสนใจมากขึ้น ถัดไป เรากลับไปที่ JSStackFrame::GetMethodName() ของ V8 เพื่อหาว่ามีผลไม้ที่ไม่ค่อยมีประโยชน์ที่เราจะลองหาจากตรงนั้นได้ไหม

  1. โดยจะทำงานโดยค้นหา "name" ของฟังก์ชันซึ่งเป็นพร็อพเพอร์ตี้ในออบเจ็กต์ก่อน และหากพบ ให้ตรวจสอบว่าค่าพร็อพเพอร์ตี้ตรงกับฟังก์ชันดังกล่าว
  2. หากฟังก์ชันไม่มีชื่อหรือออบเจ็กต์ไม่มีพร็อพเพอร์ตี้ที่ตรงกัน ฟังก์ชันจะกลับไปใช้การค้นหาแบบย้อนกลับโดยข้ามผ่านพร็อพเพอร์ตี้ทั้งหมดของออบเจ็กต์และต้นแบบ

ในตัวอย่างของเรา ฟังก์ชันทั้งหมดเป็นแบบไม่ระบุตัวตนและมีพร็อพเพอร์ตี้ "name" ที่ว่างเปล่า

A.SDV = function() {
   // ...
};

ผลการวิจัยแรกคือการค้นหาแบบย้อนกลับแบ่งออกเป็น 2 ขั้นตอน (สำหรับตัววัตถุเองและวัตถุแต่ละชิ้นในสายต้นแบบ)

  1. ดึงชื่อของคุณสมบัติที่แจกแจงได้ทั้งหมด และ
  2. ทำการค้นหาพร็อพเพอร์ตี้ทั่วไปสำหรับแต่ละชื่อ ทดสอบว่าค่าพร็อพเพอร์ตี้ที่เป็นผลลัพธ์ตรงกับการปิดที่ต้องการหรือไม่

ดูเหมือนจะเป็นผลไม้ที่แทบจะไร้ปัญหา เนื่องจากการแยกชื่อออกมาต้องเดินดูคุณสมบัติทั้งหมดก่อน แทนที่จะทำ 2 ผ่านคือ O(N) สำหรับการแยกชื่อและบันทึก O(N) สำหรับการทดสอบ เราสามารถทำทุกอย่างได้ในขั้นตอนเดียวและตรวจสอบค่าพร็อพเพอร์ตี้โดยตรง ทำให้ฟังก์ชันทั้งหมดเร็วขึ้นประมาณ 2-10 เท่า

ผลการค้นพบที่ 2 น่าสนใจยิ่งขึ้น แม้ในทางเทคนิคแล้ว ฟังก์ชันเหล่านี้จะเป็นฟังก์ชันที่ไม่ระบุตัวตน แต่เครื่องมือ V8 ได้บันทึกสิ่งที่เราเรียกว่าชื่อที่อนุมานไว้ สำหรับลิเทอรัลฟังก์ชันที่ปรากฏทางด้านขวาของการมอบหมายในรูปแบบ obj.foo = function() {...} โปรแกรมแยกวิเคราะห์ V8 จะจดจำ "obj.foo" เป็นชื่อที่อนุมานสำหรับฟังก์ชันลิเทอรัล ดังนั้นในกรณีของเรา แม้ว่าจะไม่มีชื่อที่เหมาะสมที่สามารถทำการค้นหาได้ แต่เราพบว่ามีชื่อที่ใกล้เพียงพอแล้ว สำหรับตัวอย่าง A.SDV = function() {...} ข้างต้น เรามี "A.SDV" เป็นชื่อที่อนุมาน และเราสามารถดึงชื่อพร็อพเพอร์ตี้จากชื่อที่อนุมานได้โดยมองหาจุดสุดท้าย แล้วค้นหาพร็อพเพอร์ตี้ "SDV" บนออบเจ็กต์ วิธีนี้ช่วยแก้ปัญหาได้เกือบทุกกรณี โดยแทนที่การเดินทางเต็มรูปแบบซึ่งมีราคาแพงด้วยการค้นหาอสังหาริมทรัพย์เพียงแห่งเดียว การปรับปรุง 2 รายการนี้ได้เป็นส่วนหนึ่งของ CL นี้ และลดชะลอตัวของตัวอย่างที่รายงานใน chromium:1069425 ลงอย่างมาก

Error.stack

เราคงจะพูดเกี่ยวกับเรื่องนี้ทั้งวันเลยก็ว่าได้ แต่มีบางอย่างที่น่าสงสัย เพราะเครื่องมือสำหรับนักพัฒนาเว็บไม่ได้ใช้ชื่อเมธอดสำหรับสแต็กเฟรม อันที่จริงแล้ว คลาส v8::StackFrame ใน C++ API ไม่ได้บอกวิธีใดๆ ในการรับชื่อเมธอด ดูเหมือนว่าเราจะโทรหา JSStackFrame::GetMethodName() ตั้งแต่แรก แต่เป็นที่เดียวที่เราใช้ (และแสดง) ชื่อเมธอดใน API สแต็กเทรซ JavaScript เพื่อทำความเข้าใจการใช้งานดังกล่าว ลองดูตัวอย่างง่ายๆ ต่อไปนี้ error-methodname.js:

function foo() {
    console.log((new Error).stack);
}

var object = {bar: foo};
object.bar();

ตอนนี้เรามีฟังก์ชัน foo ซึ่งติดตั้งภายใต้ชื่อ "bar" ใน object การเรียกใช้ข้อมูลโค้ดนี้ใน Chromium จะให้ผลลัพธ์ดังต่อไปนี้

Error
    at Object.foo [as bar] (error-methodname.js:2)
    at error-methodname.js:6

เราจะเห็นการค้นหาชื่อเมธอดขณะเล่น โดยระบบแสดงสแต็กเฟรมที่อยู่บนสุดเพื่อเรียกใช้ฟังก์ชัน foo ในอินสแตนซ์ของ Object ผ่านเมธอดชื่อ bar ดังนั้นพร็อพเพอร์ตี้ error.stack ที่ไม่ใช่แบบมาตรฐานจึงใช้ JSStackFrame::GetMethodName() เป็นจำนวนมาก และอันที่จริงแล้วการทดสอบประสิทธิภาพของเรายังชี้ให้เห็นว่าการเปลี่ยนแปลงของเราทำให้สิ่งต่างๆ เร็วขึ้นมากด้วย

เร่งความเร็วของการเปรียบเทียบแบบไมโครของ StackTrace

ทีนี้กลับไปที่หัวข้อเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ระบบคำนวณชื่อเมธอดแม้ว่าจะไม่ได้ใช้ error.stack ก็ตาม สิ่งที่ช่วยเราได้มีดังนี้ เดิมที V8 มีกลไกที่แตกต่างกัน 2 กลไกในการรวบรวมและเป็นตัวแทนของสแต็กเทรซสำหรับ API ที่ต่างกัน 2 รายการที่อธิบายไว้ข้างต้น (API ของ v8::StackFrame สำหรับ C++ และ API สแต็กเทรซ JavaScript) การมี 2 วิธีที่ทำได้เหมือนกัน (คร่าวๆ) มักเกิดข้อผิดพลาดและมักทำให้เกิดความไม่สอดคล้องและข้อบกพร่อง ดังนั้นในช่วงปลายปี 2018 เราจึงเริ่มโปรเจ็กต์ที่ต้องแก้ปัญหาคอขวดเดียวในการจับภาพสแต็กเทรซ

โครงการดังกล่าวประสบความสำเร็จอย่างมากและลดจำนวนปัญหาเกี่ยวกับการรวบรวมสแต็กเทรซลงอย่างมาก ข้อมูลส่วนใหญ่ที่ให้ผ่านพร็อพเพอร์ตี้ error.stack ที่ไม่เป็นมาตรฐานยังได้รับการคำนวณแบบ Lazy Loading และเมื่อจำเป็นจริงๆ เท่านั้น แต่ในขั้นตอนการเปลี่ยนโครงสร้างวัตถุ เราได้ใช้กลวิธีเดียวกันนี้กับออบเจ็กต์ v8::StackFrame รายการ ระบบจะคำนวณข้อมูลทั้งหมดเกี่ยวกับสแต็กเฟรมเมื่อมีการเรียกใช้เมธอดเป็นครั้งแรก

โดยทั่วไปแล้วการทำเช่นนี้จะช่วยปรับปรุงประสิทธิภาพ แต่ปรากฏว่ามีความขัดแย้งกับวิธีการใช้ออบเจ็กต์ API ของ C++ เหล่านี้ใน Chromium และเครื่องมือสำหรับนักพัฒนาเว็บ โดยเฉพาะอย่างยิ่ง ตั้งแต่ที่เราได้แนะนำคลาส v8::internal::StackFrameInfo ใหม่ซึ่งเก็บข้อมูลทั้งหมดเกี่ยวกับสแต็กเฟรมที่แสดงผ่าน v8::StackFrame หรือผ่าน error.stack เราจึงจะคำนวณชุดขั้นสูงของข้อมูลที่ได้รับจาก API ทั้งสองเสมอ ซึ่งหมายความว่าสำหรับการใช้งาน v8::StackFrame (และโดยเฉพาะสำหรับ DevTools) เราจะคำนวณชื่อเมธอดทันทีที่มีการขอข้อมูลเกี่ยวกับสแต็กเฟรม ดูเหมือนว่าเครื่องมือสำหรับนักพัฒนาเว็บจะขอข้อมูลแหล่งที่มาและสคริปต์โดยทันทีเสมอ

จากการรับรู้ดังกล่าว เราสามารถเปลี่ยนโครงสร้างภายในและแปลงการนำเสนอสแต็กเฟรมให้ง่ายขึ้นอย่างมาก และทำให้ทำงานแบบ Lazy Loading ได้มากยิ่งขึ้น ดังนั้นการใช้งานทั่วทั้ง V8 และ Chromium จะจ่ายเฉพาะค่าใช้จ่ายในการประมวลผลข้อมูลที่ขอเท่านั้น วิธีนี้ช่วยเพิ่มประสิทธิภาพให้กับเครื่องมือสำหรับนักพัฒนาเว็บและกรณีการใช้งานอื่นๆ ของ Chromium ได้อย่างมาก ซึ่งต้องการเพียงข้อมูลส่วนเล็กๆ เกี่ยวกับสแต็กเฟรม (โดยเฉพาะอย่างยิ่งชื่อสคริปต์และตำแหน่งแหล่งที่มาในรูปแบบออฟเซ็ตเส้นและคอลัมน์) และเปิดประตูสู่การปรับปรุงประสิทธิภาพให้ดียิ่งขึ้น

ชื่อฟังก์ชัน

จากการปรับโครงสร้างที่กล่าวไปข้างต้น ทำให้มีค่าใช้จ่ายในการแทนที่ด้วยสัญลักษณ์ (เวลาที่ใช้ในเดือนv8_inspector::V8Debugger::symbolize) ลดลงเหลือประมาณ 15% ของเวลาดำเนินการโดยรวม และเราเห็นได้ชัดเจนขึ้นว่า V8 ใช้เวลาไปกับส่วนใดเมื่อ (รวบรวมและ) เป็นสัญลักษณ์ของสแต็กเฟรมเพื่อการบริโภคในเครื่องมือสำหรับนักพัฒนาเว็บ

ต้นทุนการแปลงข้อมูลเป็นสัญลักษณ์

สิ่งแรกที่โดดเด่นคือค่าใช้จ่ายสะสมสำหรับหมายเลขบรรทัดและคอลัมน์ ส่วนที่แพงในที่นี้คือการคำนวณออฟเซ็ตอักขระภายในสคริปต์ (อิงตามออฟเซ็ตไบต์โค้ดที่ได้จาก V8) และปรากฏว่าเนื่องจากเราทำการเปลี่ยนโครงสร้างโค้ดข้างต้น 2 ครั้ง คือครั้งหนึ่งคำนวณเลขบรรทัด และอีกครั้งหนึ่งเมื่อคำนวณหมายเลขคอลัมน์ การแคชตำแหน่งแหล่งที่มาในอินสแตนซ์ v8::internal::StackFrameInfo ช่วยแก้ไขปัญหานี้ได้อย่างรวดเร็วและลบ v8::internal::StackFrameInfo::GetColumnNumber ออกจากโปรไฟล์ใดๆ ได้โดยสมบูรณ์

ข้อมูลที่น่าสนใจยิ่งขึ้นสำหรับเราคือ v8::StackFrame::GetFunctionName อยู่ในอันดับที่สูงอย่างน่าแปลกใจในโปรไฟล์ทั้งหมดที่เราดู เมื่อเจาะลึกลงไปแล้วพบว่าการประมวลผลชื่อที่เราจะแสดงสำหรับฟังก์ชันในสแต็กเฟรมในเครื่องมือสำหรับนักพัฒนาเว็บมีค่าใช้จ่ายสูงเกินความจำเป็น

  1. ค้นหาพร็อพเพอร์ตี้ "displayName" ที่ไม่เป็นไปตามมาตรฐานก่อน และหากให้พร็อพเพอร์ตี้ข้อมูลที่มีค่าสตริง เราจะใช้พร็อพเพอร์ตี้นั้น
  2. ไม่เช่นนั้นให้กลับไปใช้พร็อพเพอร์ตี้ "name" มาตรฐาน แล้วตรวจสอบอีกครั้งว่าพร็อพเพอร์ตี้ดังกล่าวให้ผลลัพธ์เป็นพร็อพเพอร์ตี้ข้อมูลที่มีค่าเป็นสตริงหรือไม่
  3. แล้วกลับไปใช้ชื่อการแก้ไขข้อบกพร่องภายในที่โปรแกรมแยกวิเคราะห์ V8 อนุมานและจัดเก็บไว้ในฟังก์ชันตามตัวอักษรแทน

มีการเพิ่มพร็อพเพอร์ตี้ "displayName" เพื่อเป็นการแก้ปัญหาสำหรับพร็อพเพอร์ตี้ "name" บนอินสแตนซ์ Function เป็นแบบอ่านอย่างเดียวและไม่สามารถกำหนดค่าได้ใน JavaScript แต่ยังไม่เคยสร้างมาตรฐานและไม่พบการใช้งานในวงกว้าง เนื่องจากเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์เบราว์เซอร์เพิ่มการสรุปชื่อฟังก์ชันซึ่งทำงานได้ใน 99.9% ของกรณีทั้งหมด นอกเหนือไปจาก ES2015 แล้ว ยังทำให้พร็อพเพอร์ตี้ "name" ในอินสแตนซ์ Function รายการกำหนดค่าได้ ทำให้ไม่จำเป็นต้องใช้พร็อพเพอร์ตี้ "displayName" พิเศษอีก เนื่องจากการค้นหาเชิงลบสำหรับ "displayName" ค่อนข้างมีค่าใช้จ่ายสูงและไม่จำเป็นจริงๆ (ES2015 เปิดตัวมากกว่า 5 ปีที่ผ่านมา) เราจึงตัดสินใจนำการสนับสนุนสำหรับพร็อพเพอร์ตี้ fn.displayName ที่ไม่เป็นไปตามมาตรฐานออกจาก V8 (และเครื่องมือสำหรับนักพัฒนาเว็บ)

เมื่อทำการค้นหาเชิงลบเกี่ยวกับ "displayName" เรียบร้อยแล้ว ค่าใช้จ่ายครึ่งหนึ่งของ v8::StackFrame::GetFunctionName ก็ถูกนำออกไป อีกครึ่งหนึ่งจะเป็นการค้นหาพร็อพเพอร์ตี้ "name" ทั่วไป แต่เรามีตรรกะที่พร้อมอยู่แล้วในการหลีกเลี่ยงการค้นหาพร็อพเพอร์ตี้ "name" ที่มีค่าใช้จ่ายสูงใน (ไม่เปลี่ยนแปลง) อินสแตนซ์ Function ซึ่งเราได้เริ่มใช้ใน V8 มาสักพักแล้วเพื่อช่วยให้ Function.prototype.bind() ทำงานเร็วขึ้น เราส่งการตรวจสอบที่จำเป็น ทำให้เราสามารถข้ามการค้นหาทั่วไปที่มีค่าใช้จ่ายได้ตั้งแต่แรก โดย v8::StackFrame::GetFunctionName จะไม่แสดงในโปรไฟล์ใดๆ ที่เราพิจารณาอีกต่อไป

บทสรุป

จากการปรับปรุงข้างต้น เราได้ลดภาระของเครื่องมือสำหรับนักพัฒนาเว็บลงได้อย่างมากในแง่ของสแต็กเทรซ

เราทราบว่ายังคงมีการปรับปรุงที่เป็นไปได้อีกมากมาย ตัวอย่างเช่น ค่าใช้จ่ายในการดำเนินการเมื่อใช้ MutationObserver ยังคงสังเกตเห็นได้ชัดเจน ดังที่รายงานใน chromium:1077657 แต่ในขณะนี้เราได้แก้ไขปัญหาหลักๆ แล้ว และเราอาจกลับมาปรับปรุงประสิทธิภาพการแก้ไขข้อบกพร่องในอนาคต

ดาวน์โหลดช่องตัวอย่าง

ลองใช้ Chrome Canary, Dev หรือเบต้าเป็นเบราว์เซอร์สำหรับการพัฒนาเริ่มต้น ช่องทางแสดงตัวอย่างเหล่านี้ช่วยให้คุณได้เข้าถึงฟีเจอร์ล่าสุดของเครื่องมือสำหรับนักพัฒนาเว็บ ทดสอบ API แพลตฟอร์มเว็บที่ล้ำสมัย และค้นหาปัญหาในเว็บไซต์ก่อนที่ผู้ใช้จะได้ใช้งาน

ติดต่อทีมเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome

ใช้ตัวเลือกต่อไปนี้เพื่อพูดคุยเกี่ยวกับฟีเจอร์ใหม่ๆ และการเปลี่ยนแปลงในโพสต์ หรือเรื่องอื่นๆ ที่เกี่ยวข้องกับเครื่องมือสำหรับนักพัฒนาเว็บ

  • ส่งคำแนะนำหรือความคิดเห็นถึงเราผ่าน crbug.com
  • รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บโดยใช้ตัวเลือกเพิ่มเติม   เพิ่มเติม   > ความช่วยเหลือ > รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บในเครื่องมือสำหรับนักพัฒนาเว็บ
  • ทวีตที่ @ChromeDevTools
  • โปรดแสดงความคิดเห็นในวิดีโอ YouTube เกี่ยวกับมีอะไรใหม่ในเครื่องมือสำหรับนักพัฒนาเว็บในวิดีโอ YouTube หรือเคล็ดลับสำหรับเครื่องมือสำหรับนักพัฒนาเว็บ