วิธีทำให้ประสิทธิภาพมองเห็นได้ด้วย GitLab CI และ Hoodoo ของ GitLab Artifacts

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างย่อ ↬ การปรับให้เหมาะสมแอปพลิเคชันไม่เพียงพอ คุณต้องป้องกันไม่ให้ประสิทธิภาพลดลง และขั้นตอนแรกที่ต้องทำคือทำให้มองเห็นการเปลี่ยนแปลงประสิทธิภาพได้ ในบทความนี้ Anton Nemtsev ได้แสดงวิธีแสดงสองวิธีในคำขอรวม GitLab

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

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

หมายเหตุ : หากคุณมีความเข้าใจพื้นฐานเกี่ยวกับ Node.js มีแนวคิดที่คลุมเครือเกี่ยวกับวิธีการทำงานของ CI/CD ของคุณ และใส่ใจเกี่ยวกับประสิทธิภาพของแอปหรือข้อได้เปรียบทางธุรกิจที่จะเกิดขึ้น เราก็พร้อมดำเนินการ

วิธีสร้างงบประมาณผลงานสำหรับโครงการ

คำถามแรกที่เราควรถามตัวเองคือ:

“โครงการการแสดงคืออะไร”

“ฉันควรใช้เมตริกใด”

“ค่าใดของเมตริกเหล่านี้ที่ยอมรับได้”

การเลือกตัววัดอยู่นอกขอบเขตของบทความนี้และขึ้นอยู่กับบริบทของโครงการเป็นอย่างมาก แต่ผมแนะนำให้คุณเริ่มต้นด้วยการอ่านตัวชี้วัดประสิทธิภาพที่เน้นผู้ใช้เป็นศูนย์กลางโดย Philip Walton

จากมุมมองของฉัน ควรใช้ขนาดของไลบรารีเป็นกิโลไบต์เป็นตัววัดสำหรับแพ็คเกจ npm ทำไม? ก็เพราะว่าถ้าคนอื่นรวมโค้ดของคุณไว้ในโปรเจ็กต์ของพวกเขา พวกเขาอาจต้องการลดผลกระทบของโค้ดของคุณที่มีต่อขนาดสุดท้ายของแอปพลิเคชันของพวกเขา

สำหรับไซต์ ฉันจะถือว่า Time To First Byte (TTFB) เป็นตัวชี้วัด เมตริกนี้แสดงระยะเวลาที่เซิร์ฟเวอร์ใช้ในการตอบสนองต่อบางสิ่ง ตัวชี้วัดนี้มีความสำคัญแต่ค่อนข้างคลุมเครือเพราะสามารถรวมทุกอย่างได้ — เริ่มตั้งแต่เวลาในการแสดงผลของเซิร์ฟเวอร์และจบลงด้วยปัญหาเวลาแฝง ดังนั้นจึงเป็นเรื่องดีที่จะใช้ร่วมกับ Server Timing หรือ OpenTracing เพื่อค้นหาว่าประกอบด้วยอะไรบ้าง

คุณควรพิจารณาเมตริกเช่น Time to Interactive (TTI) และ First Meaningful Paint (เร็วๆ นี้จะถูกแทนที่ด้วย Largest Contentful Paint (LCP)) ฉันคิดว่าทั้งสองสิ่งนี้สำคัญที่สุด - จากมุมมองของการรับรู้ประสิทธิภาพ

แต่โปรดทราบว่า: เมตริกมักเกี่ยวข้องกับบริบท ดังนั้นโปรดอย่าถือเอาว่าสิ่งนี้เป็นเหตุเป็นผล คิดถึงสิ่งที่สำคัญในกรณีของคุณโดยเฉพาะ

วิธีที่ง่ายที่สุดในการกำหนดค่าที่ต้องการสำหรับตัวชี้วัดคือการใช้คู่แข่งของคุณ หรือแม้แต่ตัวคุณเอง นอกจากนี้ ในบางครั้ง เครื่องมือเช่นเครื่องคำนวณงบประมาณประสิทธิภาพอาจมีประโยชน์ — ลองใช้ดูสักหน่อย

การเสื่อมประสิทธิภาพเป็นปัญหาที่เราเผชิญทุกวัน เราอาจใช้ความพยายามเพื่อทำให้แอปพลิเคชันทำงานได้อย่างรวดเร็ว แต่ในไม่ช้า เราก็มาถึงจุดที่เราเริ่มต้น

ใช้คู่แข่งเพื่อผลประโยชน์ของคุณ

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

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

  1. วัดค่าของตัวชี้วัดที่คุณเลือกในหน้าแต่ละประเภทสำหรับโครงการของคู่แข่งของคุณ
  2. วัดเมตริกเดียวกันในโครงการของคุณ
  3. ค้นหาสิ่งที่ดีกว่าค่าของคุณมากที่สุดสำหรับแต่ละตัวชี้วัดในโครงการของคู่แข่ง เพิ่ม 20% ให้กับพวกเขาและตั้งเป็นเป้าหมายต่อไปของคุณ

ทำไมต้อง 20% นี่เป็นตัวเลขมหัศจรรย์ที่คาดว่าจะเห็นความแตกต่างได้ด้วยตาเปล่า คุณสามารถอ่านเพิ่มเติมเกี่ยวกับตัวเลขนี้ได้ในบทความของ Denys Mishunov เรื่อง “ทำไมการรับรู้ถึงเรื่องสำคัญ ตอนที่ 1: การรับรู้ของเวลา”

การต่อสู้ด้วยเงา

คุณมีโครงการที่ไม่เหมือนใครหรือไม่? ไม่มีคู่แข่ง? หรือคุณมีอยู่แล้วดีกว่าพวกเขาในทุกความรู้สึก? มันไม่ใช่ประเด็น คุณสามารถแข่งขันกับคู่ต่อสู้ที่คู่ควรเท่านั้น นั่นคือตัวคุณเอง วัดผลการวัดประสิทธิภาพแต่ละรายการของโครงการของคุณในหน้าแต่ละประเภท แล้วปรับปรุงให้ดีขึ้น 20% เท่ากัน

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

การทดสอบสังเคราะห์

มีสองวิธีในการวัดประสิทธิภาพ:

  • สังเคราะห์ (ในสภาพแวดล้อมที่มีการควบคุม)
  • RUM (การวัดผู้ใช้จริง)
    กำลังรวบรวมข้อมูลจากผู้ใช้จริงในการผลิต

ในบทความนี้ เราจะใช้การทดสอบสังเคราะห์และถือว่าโปรเจ็กต์ของเราใช้ GitLab ที่มี CI ในตัวสำหรับการปรับใช้โปรเจ็กต์

ห้องสมุดและขนาดเป็นตัวชี้วัด

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

แพ็คเกจวัดขนาดห้องสมุด

เพื่อรักษาขนาดของไลบรารีให้เล็กที่สุดเท่าที่จะเป็นไปได้ เราต้องจับตาดูให้ดีว่ามันเปลี่ยนแปลงไปอย่างไรในช่วงเวลาการพัฒนา แต่คุณจะทำอย่างไร? เราสามารถใช้ package Size Limit ที่สร้างโดย Andrey Sitnik จาก Evil Martians ได้

มาติดตั้งกันเลย

 npm i -D size-limit @size-limit/preset-small-lib

จากนั้นเพิ่มลงใน package.json

 "scripts": { + "size": "size-limit", "test": "jest && eslint ." }, + "size-limit": [ + { + "path": "index.js" + } + ],

บล็อก "size-limit":[{},{},…] มีรายการขนาดของไฟล์ที่เราต้องการตรวจสอบ ในกรณีของเรา เป็นเพียงไฟล์เดียว: index.js

size สคริปต์ NPM เพียงรันแพ็คเกจ size-limit ซึ่งอ่านการกำหนดค่า block size-limit ที่กล่าวถึงก่อนหน้านี้ และตรวจสอบขนาดของไฟล์ที่แสดงอยู่ที่นั่น เรียกใช้และดูว่าเกิดอะไรขึ้น:

 npm run size 
ผลลัพธ์ของการดำเนินการคำสั่งแสดงขนาดของ index.js
ผลลัพธ์ของการดำเนินการคำสั่งแสดงขนาดของ index.js (ตัวอย่างขนาดใหญ่)

เราสามารถเห็นขนาดของไฟล์ได้ แต่จริงๆ แล้วขนาดนี้ไม่ได้อยู่ภายใต้การควบคุม มาแก้ไขโดยเพิ่ม limit ให้กับ package.json :

 "size-limit": [ { + "limit": "2 KB", "path": "index.js" } ],

ตอนนี้ หากเราเรียกใช้สคริปต์ สคริปต์จะถูกตรวจสอบกับขีดจำกัดที่เราตั้งไว้

ภาพหน้าจอของเทอร์มินัล ขนาดของไฟล์น้อยกว่าขีดจำกัดและแสดงเป็นสีเขียว
ภาพหน้าจอของเทอร์มินัล ขนาดของไฟล์น้อยกว่าขีดจำกัดและแสดงเป็นสีเขียว (ตัวอย่างขนาดใหญ่)

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

ภาพหน้าจอของเทอร์มินัลที่มีขนาดไฟล์เกินขีดจำกัดและแสดงเป็นสีแดง สคริปต์เสร็จสิ้นด้วยรหัสที่ไม่ใช่ศูนย์
ภาพหน้าจอของเทอร์มินัลที่มีขนาดไฟล์เกินขีดจำกัดและแสดงเป็นสีแดง สคริปต์เสร็จสิ้นด้วยรหัสที่ไม่ใช่ศูนย์ (ตัวอย่างขนาดใหญ่)

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

มาติดตั้งกันเลย

 npm i -D husky

จากนั้นแก้ไข package.json ของเรา

 "size-limit": [ { "limit": "2 KB", "path": "index.js" } ], + "husky": { + "hooks": { + "pre-commit": "npm run size" + } + },

และตอนนี้ก่อนที่การคอมมิตแต่ละครั้งโดยอัตโนมัติจะถูกดำเนินการ npm run size คำสั่งและหากมันจะลงท้ายด้วยโค้ดที่ไม่เป็นศูนย์ การคอมมิทก็จะไม่เกิดขึ้น

ภาพหน้าจอของเทอร์มินัลที่การคอมมิตถูกยกเลิกเนื่องจากขนาดของไฟล์เกินขีดจำกัด
ภาพหน้าจอของเทอร์มินัลที่การคอมมิตถูกยกเลิกเนื่องจากขนาดของไฟล์เกินขีดจำกัด (ตัวอย่างขนาดใหญ่)

แต่มีหลายวิธีในการข้ามตะขอ (โดยตั้งใจหรือโดยบังเอิญ) ดังนั้นเราจึงไม่ควรพึ่งพาตะขอมากเกินไป

นอกจากนี้ สิ่งสำคัญที่ควรทราบคือเราไม่จำเป็นต้องทำการบล็อกการตรวจสอบนี้ ทำไม? เพราะเป็นเรื่องปกติที่ขนาดของไลบรารีจะเพิ่มขึ้นในขณะที่คุณเพิ่มคุณสมบัติใหม่ เราจำเป็นต้องทำให้การเปลี่ยนแปลงปรากฏ นั่นคือทั้งหมด ซึ่งจะช่วยหลีกเลี่ยงการเพิ่มขนาดโดยไม่ได้ตั้งใจเนื่องจากการแนะนำไลบรารีตัวช่วยที่เราไม่ต้องการ และบางทีอาจทำให้นักพัฒนาและเจ้าของผลิตภัณฑ์มีเหตุผลในการพิจารณาว่าคุณลักษณะที่เพิ่มเข้ามานั้นคุ้มค่ากับการเพิ่มขนาดหรือไม่ หรืออาจจะมีแพ็คเกจอื่นที่มีขนาดเล็กกว่าหรือไม่ Bundlephobia ช่วยให้เราสามารถหาทางเลือกอื่นสำหรับแพ็คเกจ NPM เกือบทั้งหมด

แล้วเราควรทำอย่างไร? มาแสดงการเปลี่ยนแปลงในขนาดไฟล์โดยตรงในคำขอรวมกันเถอะ! แต่คุณไม่ได้ผลักดันให้เชี่ยวชาญโดยตรง คุณทำตัวเหมือนนักพัฒนาที่โตแล้วใช่ไหม

ดำเนินการตรวจสอบ GitLab CI . ของเรา

มาเพิ่มอาร์ติแฟกต์ GitLab ของประเภทเมทริกกันเถอะ อาร์ติแฟกต์คือไฟล์ซึ่งจะ «ใช้งานได้» หลังจากการดำเนินการไปป์ไลน์เสร็จสิ้น สิ่งประดิษฐ์ประเภทนี้ช่วยให้เราสามารถแสดงวิดเจ็ตเพิ่มเติมในคำขอการรวม โดยแสดงการเปลี่ยนแปลงใดๆ ในค่าของเมตริกระหว่างสิ่งประดิษฐ์ในต้นแบบและสาขาคุณลักษณะ รูปแบบของสิ่งประดิษฐ์เมท metrics เป็นรูปแบบข้อความโพร สำหรับค่า GitLab ภายในอาร์ติแฟกต์ จะเป็นเพียงแค่ข้อความ GitLab ไม่เข้าใจว่ามูลค่าที่เปลี่ยนแปลงไปคืออะไร เพียงแต่รู้ว่าค่านั้นแตกต่างกัน แล้วเราควรทำอย่างไรดี?

  1. กำหนดสิ่งประดิษฐ์ในไปป์ไลน์
  2. เปลี่ยนสคริปต์เพื่อสร้างสิ่งประดิษฐ์บนไปป์ไลน์

ในการสร้างสิ่งประดิษฐ์ เราต้องเปลี่ยน .gitlab-ci.yml ด้วยวิธีนี้:

 image: node:latest stages: - performance sizecheck: stage: performance before_script: - npm ci script: - npm run size + artifacts: + expire_in: 7 days + paths: + - metric.txt + reports: + metrics: metric.txt
  1. expire_in: 7 days — สิ่งประดิษฐ์จะมีอายุ 7 วัน
  2.  paths: metric.txt

    มันจะถูกบันทึกไว้ในแค็ตตาล็อกรูท หากคุณข้ามตัวเลือกนี้ จะไม่สามารถดาวน์โหลดได้
  3.  reports: metrics: metric.txt

    สิ่งประดิษฐ์จะมีประเภท reports:metrics

ตอนนี้ มาทำให้ Size Limit สร้างรายงานกัน ในการทำเช่นนั้นเราต้องเปลี่ยน package.json :

 "scripts": { - "size": "size-limit", + "size": "size-limit --json > size-limit.json", "test": "jest && eslint ." },

size-limit with key --json จะส่งออกข้อมูลในรูปแบบ json:

คำสั่ง size-limit --json ส่งออก JSON ไปยังคอนโซล JSON มีอาร์เรย์ของอ็อบเจ็กต์ที่มีชื่อไฟล์และขนาด รวมทั้งแจ้งให้เราทราบว่าไฟล์นั้นเกินขีดจำกัดขนาดหรือไม่
คำสั่ง size-limit --json ส่งออก JSON ไปยังคอนโซล JSON มีอาร์เรย์ของอ็อบเจ็กต์ที่มีชื่อไฟล์และขนาด รวมทั้งแจ้งให้เราทราบว่าไฟล์นั้นเกินขีดจำกัดขนาดหรือไม่ (ตัวอย่างขนาดใหญ่)

และการเปลี่ยนเส้นทาง > size-limit.json จะบันทึก JSON ลงในไฟล์ size-limit.json

ตอนนี้เราต้องสร้างสิ่งประดิษฐ์จากสิ่งนี้ รูปแบบลดลงเหลือ [metrics name][space][metrics value] มาสร้างสคริปต์ generate-metric.js กันเถอะ:

 const report = require('./size-limit.json'); process.stdout.write(`size ${(report[0].size/1024).toFixed(1)}Kb`); process.exit(0);

และเพิ่มลงใน package.json :

 "scripts": { "size": "size-limit --json > size-limit.json", + "postsize": "node generate-metric.js > metric.txt", "test": "jest && eslint ." },

เนื่องจากเราใช้คำนำหน้า post คำสั่ง npm run size จะรันสคริปต์ size ก่อน จากนั้นจึงรันสคริปต์ postsize โดยอัตโนมัติ ซึ่งจะส่งผลให้เกิดการสร้างไฟล์ metric.txt ซึ่งเป็นสิ่งประดิษฐ์ของเรา

ด้วยเหตุนี้ เมื่อเรารวมสาขานี้เป็นต้นแบบ เปลี่ยนแปลงบางอย่าง และสร้างคำขอรวมใหม่ เราจะเห็นสิ่งต่อไปนี้:

สกรีนช็อตพร้อมคำขอรวม ซึ่งแสดงวิดเจ็ตที่มีค่าเมตริกใหม่และเก่าในวงเล็บกลม
ภาพหน้าจอพร้อมคำขอรวม ซึ่งแสดงวิดเจ็ตที่มีค่าเมตริกใหม่และเก่าในวงเล็บเหลี่ยม (ตัวอย่างขนาดใหญ่)

ในวิดเจ็ตที่ปรากฏบนหน้าเว็บ อันดับแรก เราจะเห็นชื่อของเมตริก ( size ) ตามด้วยค่าของเมตริกในสาขาคุณลักษณะ ตลอดจนค่าในต้นแบบภายในวงเล็บกลม

ตอนนี้เราสามารถเห็นวิธีการเปลี่ยนขนาดของแพ็คเกจและตัดสินใจได้อย่างสมเหตุสมผลว่าเราควรรวมเข้าด้วยกันหรือไม่

  • คุณอาจเห็นรหัสทั้งหมดนี้ในที่เก็บนี้

ประวัติย่อ

ตกลง! เราก็เลยหาวิธีจัดการกับคดีเล็กๆ น้อยๆ ได้แล้ว หากคุณมีหลายไฟล์ ให้แยกเมตริกด้วยการขึ้นบรรทัดใหม่ ทางเลือกอื่นสำหรับการจำกัดขนาด คุณอาจพิจารณาขนาดบันเดิล หากคุณกำลังใช้ WebPack คุณอาจได้ขนาดทั้งหมดที่คุณต้องการโดยสร้างด้วยแฟล็ก --profile และ --json :

 webpack --profile --json > stats.json

หากคุณกำลังใช้ next.js คุณสามารถใช้ปลั๊กอิน @next/bundle-analyzer ได้ มันขึ้นอยู่กับคุณ!

การใช้ประภาคาร

Lighthouse เป็นมาตรฐานโดยพฤตินัยในการวิเคราะห์โครงการ มาเขียนสคริปต์ที่ช่วยให้เราสามารถวัดประสิทธิภาพ a11y แนวทางปฏิบัติที่ดีที่สุด และให้คะแนน SEO แก่เรา

สคริปต์เพื่อวัดทุกสิ่ง

ในการเริ่มต้น เราต้องติดตั้งแพ็คเกจประภาคารซึ่งจะทำการวัด เรายังต้องติดตั้ง puppeteer ที่เราจะใช้เป็น headless-browser

 npm i -D lighthouse puppeteer

ต่อไป มาสร้างสคริปต์ lighthouse.js และเริ่มเบราว์เซอร์ของเรา:

 const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], }); })();

ตอนนี้ มาเขียนฟังก์ชันที่จะช่วยเราวิเคราะห์ URL ที่กำหนด:

 const lighthouse = require('lighthouse'); const DOMAIN = process.env.DOMAIN; const buildReport = browser => async url => { const data = await lighthouse( `${DOMAIN}${url}`, { port: new URL(browser.wsEndpoint()).port, output: 'json', }, { extends: 'lighthouse:full', } ); const { report: reportJSON } = data; const report = JSON.parse(reportJSON); // … }

ยอดเยี่ยม! ขณะนี้ เรามีฟังก์ชันที่จะยอมรับอ็อบเจ็กต์เบราว์เซอร์เป็นอาร์กิวเมนต์ และส่งคืนฟังก์ชันที่จะยอมรับ URL เป็นอาร์กิวเมนต์ และสร้างรายงานหลังจากส่ง URL นั้นไปยัง lighthouse

เรากำลังส่งข้อโต้แย้งต่อไปนี้ไปยัง lighthouse :

  1. ที่อยู่ที่เราต้องการวิเคราะห์
  2. ตัวเลือก lighthouse port เบราว์เซอร์โดยเฉพาะ และ output (รูปแบบเอาต์พุตของรายงาน)
  3. report การกำหนดค่าและ lighthouse:full (ทั้งหมดที่เราวัดได้) สำหรับการกำหนดค่าที่แม่นยำยิ่งขึ้น โปรดดูเอกสารประกอบ

มหัศจรรย์! ตอนนี้เรามีรายงานของเราแล้ว แต่เราจะทำอะไรกับมันได้บ้าง? เราสามารถตรวจเมตริกเทียบกับขีดจำกัดและออกจากสคริปต์ด้วยโค้ดที่ไม่เป็นศูนย์ซึ่งจะหยุดไปป์ไลน์:

 if (report.categories.performance.score < 0.8) process.exit(1);

แต่เราแค่ต้องการให้ประสิทธิภาพมองเห็นได้และไม่ปิดกั้นใช่หรือไม่ ลองใช้สิ่งประดิษฐ์ประเภทอื่น: สิ่งประดิษฐ์ประสิทธิภาพ GitLab

GitLab Performance Artifact

เพื่อให้เข้าใจรูปแบบสิ่งประดิษฐ์นี้ เราต้องอ่านโค้ดของปลั๊กอิน sitespeed.io (เหตุใด GitLab จึงอธิบายรูปแบบของสิ่งประดิษฐ์ในเอกสารของตนเองไม่ ได้ ลึกลับ )

 [ { "subject":"/", "metrics":[ { "name":"Transfer Size (KB)", "value":"19.5", "desiredSize":"smaller" }, { "name":"Total Score", "value":92, "desiredSize":"larger" }, {…} ] }, {…} ]

อาร์ติแฟกต์คือไฟล์ JSON ที่มีอาร์เรย์ของอ็อบเจ็กต์ แต่ละรายการแสดงถึงรายงานเกี่ยวกับ URL หนึ่งรายการ

 [{page 1}, {page 2}, …]

แต่ละหน้าจะแสดงโดยวัตถุที่มีคุณสมบัติดังต่อไปนี้:

  1. subject
    ตัวระบุหน้า (ค่อนข้างสะดวกที่จะใช้ชื่อพาธดังกล่าว);
  2. metrics
    อาร์เรย์ของออบเจ็กต์ (แต่ละรายการแทนการวัดหนึ่งรายการที่สร้างขึ้นบนหน้า)
 { "subject":"/login/", "metrics":[{measurement 1}, {measurement 2}, {measurement 3}, …] }

การ measurement เป็นวัตถุที่มีคุณสมบัติดังต่อไปนี้:

  1. name
    ชื่อการวัด เช่น อาจเป็น Time to first byte หรือ Time to interactive
  2. value
    ผลการวัดเชิงตัวเลข
  3. desiredSize
    หากค่าเป้าหมายควรน้อยที่สุดเท่าที่เป็นไปได้ เช่น สำหรับเมตริก Time to interactive ค่าควร smaller นี้ หากควรมีขนาดใหญ่ที่สุด เช่น สำหรับ Performance score ประภาคาร ให้ใช้ larger
 { "name":"Time to first byte (ms)", "value":240, "desiredSize":"smaller" }

มาแก้ไขฟังก์ชัน buildReport ของเราในลักษณะที่จะส่งกลับรายงานสำหรับหนึ่งหน้าพร้อมเมตริกประภาคารมาตรฐาน

ภาพหน้าจอพร้อมรายงานประภาคาร มีคะแนนประสิทธิภาพ คะแนน a11y คะแนนแนวทางปฏิบัติที่ดีที่สุด คะแนน SEO
ภาพหน้าจอพร้อมรายงานประภาคาร มีคะแนนประสิทธิภาพ คะแนน a11y คะแนนแนวทางปฏิบัติที่ดีที่สุด คะแนน SEO (ตัวอย่างขนาดใหญ่)
 const buildReport = browser => async url => { // … const metrics = [ { name: report.categories.performance.title, value: report.categories.performance.score, desiredSize: 'larger', }, { name: report.categories.accessibility.title, value: report.categories.accessibility.score, desiredSize: 'larger', }, { name: report.categories['best-practices'].title, value: report.categories['best-practices'].score, desiredSize: 'larger', }, { name: report.categories.seo.title, value: report.categories.seo.score, desiredSize: 'larger', }, { name: report.categories.pwa.title, value: report.categories.pwa.score, desiredSize: 'larger', }, ]; return { subject: url, metrics: metrics, }; }

ตอนนี้เมื่อเรามีฟังก์ชั่นที่สร้างรายงาน มาประยุกต์ใช้กับเพจแต่ละประเภทของโครงการกัน อันดับแรก ฉันต้องระบุว่า process.env.DOMAIN ควรมีโดเมนการแสดงละคร (ซึ่งคุณต้องทำให้โปรเจ็กต์ของคุณใช้งานได้จากสาขาคุณลักษณะล่วงหน้า)

 + const fs = require('fs'); const lighthouse = require('lighthouse'); const puppeteer = require('puppeteer'); const DOMAIN = process.env.DOMAIN; const buildReport = browser => async url => {/* … */}; + const urls = [ + '/inloggen', + '/wachtwoord-herstellen-otp', + '/lp/service', + '/send-request-to/ww-tammer', + '/post-service-request/binnenschilderwerk', + ]; (async () => { const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], }); + const builder = buildReport(browser); + const report = []; + for (let url of urls) { + const metrics = await builder(url); + report.push(metrics); + } + fs.writeFileSync(`./performance.json`, JSON.stringify(report)); + await browser.close(); })();
  • คุณสามารถค้นหาซอร์สแบบเต็มได้ใน gist และตัวอย่างการทำงานใน repository นี้

หมายเหตุ : ณ จุดนี้ คุณอาจต้องการขัดจังหวะฉันและกรีดร้องอย่างไร้ประโยชน์ "ทำไมคุณถึงใช้เวลาของฉัน - คุณไม่สามารถใช้ Promise.all ได้อย่างถูกต้อง!" ในการป้องกันของฉัน ฉันกล้าพูดว่า ไม่แนะนำให้เรียกใช้ประภาคารมากกว่าหนึ่งอินสแตนซ์ในเวลาเดียวกัน เพราะสิ่งนี้ส่งผลเสียต่อความแม่นยำของผลการวัด นอกจากนี้ หากคุณไม่แสดงความเฉลียวฉลาด เนื่องจากจะทำให้เกิดข้อยกเว้น

การใช้หลายกระบวนการ

คุณยังคงอยู่ในการวัดแบบขนานหรือไม่? ได้ คุณอาจต้องการใช้โหนดคลัสเตอร์ (หรือแม้แต่ Worker Threads ถ้าคุณชอบเล่นตัวหนา) แต่ก็ควรที่จะพูดคุยถึงเรื่องนี้เฉพาะในกรณีที่ไปป์ไลน์ของคุณทำงานบนสภาพแวดล้อมด้วยคอร์ที่มีอยู่หลายตัว และถึงอย่างนั้น คุณควรจำไว้ว่าเนื่องจากลักษณะของ Node.js คุณจะมีอินสแตนซ์ Node.js แบบเต็มน้ำหนักที่เกิดขึ้นในแต่ละกระบวนการแยก (แทนที่จะใช้อันเดิมซ้ำซึ่งจะทำให้มีการใช้ RAM เพิ่มขึ้น) ทั้งหมดนี้หมายความว่าจะมีค่าใช้จ่ายเพิ่มขึ้นเนื่องจากความต้องการฮาร์ดแวร์ที่เพิ่มขึ้นและเร็วขึ้นเล็กน้อย อาจดูเหมือนว่าเกมไม่คุ้มเทียน

หากคุณต้องการเสี่ยง คุณจะต้อง:

  1. แยกอาร์เรย์ URL ออกเป็นชิ้น ๆ ตามหมายเลขคอร์
  2. สร้างทางแยกของกระบวนการตามจำนวนแกน
  3. ถ่ายโอนส่วนต่างๆ ของอาร์เรย์ไปที่ fork แล้วดึงรายงานที่สร้างขึ้น

หากต้องการแยกอาร์เรย์ คุณสามารถใช้วิธีหลายส่วนได้ รหัสต่อไปนี้ - เขียนในเวลาเพียงไม่กี่นาที - จะไม่เลวร้ายไปกว่ารหัสอื่น:

 /** * Returns urls array splited to chunks accordin to cors number * * @param urls {String[]} — URLs array * @param cors {Number} — count of available cors * @return {Array } — URLs array splited to chunks */ function chunkArray(urls, cors) { const chunks = [...Array(cors)].map(() => []); let index = 0; urls.forEach((url) => { if (index > (chunks.length - 1)) { index = 0; } chunks[index].push(url); index += 1; }); return chunks; } /** * Returns urls array splited to chunks accordin to cors number * * @param urls {String[]} — URLs array * @param cors {Number} — count of available cors * @return {Array } — URLs array splited to chunks */ function chunkArray(urls, cors) { const chunks = [...Array(cors)].map(() => []); let index = 0; urls.forEach((url) => { if (index > (chunks.length - 1)) { index = 0; } chunks[index].push(url); index += 1; }); return chunks; }

ทำส้อมตามจำนวนแกน:

 // Adding packages that allow us to use cluster const cluster = require('cluster'); // And find out how many cors are available. Both packages are build-in for node.js. const numCPUs = require('os').cpus().length; (async () => { if (cluster.isMaster) { // Parent process const chunks = chunkArray(urls, urls.length/numCPUs); chunks.map(chunk => { // Creating child processes const worker = cluster.fork(); }); } else { // Child process } })();

มาถ่ายโอนอาร์เรย์ของส่วนย่อยไปยังกระบวนการย่อยและดึงรายงานกลับมา:

 (async () => { if (cluster.isMaster) { // Parent process const chunks = chunkArray(urls, urls.length/numCPUs); chunks.map(chunk => { const worker = cluster.fork(); + // Send message with URL's array to child process + worker.send(chunk); }); } else { // Child process + // Recieveing message from parent proccess + process.on('message', async (urls) => { + const browser = await puppeteer.launch({ + args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], + }); + const builder = buildReport(browser); + const report = []; + for (let url of urls) { + // Generating report for each URL + const metrics = await builder(url); + report.push(metrics); + } + // Send array of reports back to the parent proccess + cluster.worker.send(report); + await browser.close(); + }); } })();

และสุดท้าย ประกอบรายงานเข้าในอาร์เรย์เดียวและสร้างสิ่งประดิษฐ์ขึ้นมา

  • ดูโค้ดและที่เก็บแบบเต็มพร้อมตัวอย่างที่แสดงวิธีใช้ lighthouse กับหลายกระบวนการ

ความแม่นยำในการวัด

เราได้ทำการวัดขนานกัน ซึ่งทำให้เกิดข้อผิดพลาดในการวัดขนาดใหญ่ที่โชคร้ายของ lighthouse มากขึ้น แต่เราจะลดมันได้อย่างไร? ทำการวัดเล็กน้อยแล้วคำนวณค่าเฉลี่ย

ในการดำเนินการดังกล่าว เราจะเขียนฟังก์ชันที่จะคำนวณค่าเฉลี่ยระหว่างผลการวัดปัจจุบันกับผลก่อนหน้า

 // Count of measurements we want to make const MEASURES_COUNT = 3; /* * Reducer which will calculate an avarage value of all page measurements * @param pages {Object} — accumulator * @param page {Object} — page * @return {Object} — page with avarage metrics values */ const mergeMetrics = (pages, page) => { if (!pages) return page; return { subject: pages.subject, metrics: pages.metrics.map((measure, index) => { let value = (measure.value + page.metrics[index].value)/2; value = +value.toFixed(2); return { ...measure, value, } }), } }

จากนั้น เปลี่ยนรหัสของเราเพื่อใช้:

 process.on('message', async (urls) => { const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--headless'], }); const builder = buildReport(browser); const report = []; for (let url of urls) { + // Let's measure MEASURES_COUNT times and calculate the avarage + let measures = []; + let index = MEASURES_COUNT; + while(index--){ const metric = await builder(url); + measures.push(metric); + } + const measure = measures.reduce(mergeMetrics); report.push(measure); } cluster.worker.send(report); await browser.close(); }); }
  • ตรวจสอบส่วนสำคัญด้วยรหัสเต็มและพื้นที่เก็บข้อมูลพร้อมตัวอย่าง

และตอนนี้เราก็สามารถเพิ่ม lighthouse ลงในท่อได้แล้ว

การเพิ่มลงในท่อ

ขั้นแรก สร้างไฟล์การกำหนดค่าชื่อ . .gitlab-ci.yml

 image: node:latest stages: # You need to deploy a project to staging and put the staging domain name # into the environment variable DOMAIN. But this is beyond the scope of this article, # primarily because it is very dependent on your specific project. # - deploy # - performance lighthouse: stage: performance before_script: - apt-get update - apt-get -y install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget - npm ci script: - node lighthouse.js artifacts: expire_in: 7 days paths: - performance.json reports: performance: performance.json

จำเป็นต้องมีแพ็คเกจที่ติดตั้งไว้หลายชุดสำหรับ puppeteer คุณอาจพิจารณาใช้ docker แทน นอกจากนั้น มันสมเหตุสมผลแล้วที่เรากำหนดประเภทของสิ่งประดิษฐ์เป็นประสิทธิภาพ และทันทีที่ทั้งมาสเตอร์และฟีเจอร์แบรนช์มี คุณจะเห็นวิดเจ็ตแบบนี้ในคำขอรวม:

สกรีนช็อตของหน้าคำขอรวม มีวิดเจ็ตที่แสดงให้เห็นว่าตัวชี้วัดประภาคารใดเปลี่ยนแปลงไปและอย่างไร
สกรีนช็อตของหน้าคำขอรวม มีวิดเจ็ตที่แสดงให้เห็นว่าตัวชี้วัดประภาคารใดเปลี่ยนแปลงไปและอย่างไร (ตัวอย่างขนาดใหญ่)

ดี?

ประวัติย่อ

ในที่สุดเราก็เสร็จสิ้นกับกรณีที่ซับซ้อนมากขึ้น เห็นได้ชัดว่ามีเครื่องมือที่คล้ายกันหลายอย่างนอกเหนือจากประภาคาร ตัวอย่างเช่น sitespeed.io เอกสารประกอบของ GitLab ยังมีบทความที่อธิบายวิธีใช้ความเร็วไซต์ในไปป์ไลน์ของ sitespeed นอกจากนี้ยังมีปลั๊กอินสำหรับ GitLab ที่ช่วยให้เราสร้างสิ่งประดิษฐ์ได้ แต่ใครจะชอบผลิตภัณฑ์โอเพ่นซอร์สที่ขับเคลื่อนโดยชุมชนมากกว่าผลิตภัณฑ์ที่มีมอนสเตอร์ของบริษัทเป็นเจ้าของ?

ไม่มีการพักผ่อนสำหรับคนชั่วร้าย

ดูเหมือนว่าในที่สุดเราก็มาถึงแล้ว แต่ยังไม่ใช่ หากคุณกำลังใช้ metrics เวอร์ชันที่ต้องชำระเงิน สิ่งประดิษฐ์ที่มีประเภทรายงานและ performance จะแสดงอยู่ในแผนตั้งแต่ premium และ silver ซึ่งมีค่าใช้จ่าย 19 เหรียญต่อเดือนสำหรับผู้ใช้แต่ละราย นอกจากนี้ คุณไม่สามารถซื้อคุณลักษณะเฉพาะที่ต้องการได้ คุณสามารถเปลี่ยนแผนได้เท่านั้น เสียใจ. แล้วเราทำอะไรได้บ้าง? แตกต่างจาก GitHub ที่มี Checks API และ Status API ของมัน GitLab จะไม่อนุญาตให้คุณสร้างวิดเจ็ตจริงในคำขอรวมด้วยตัวเอง และไม่มีความหวังที่จะได้รับพวกเขาในเร็ว ๆ นี้

ภาพหน้าจอของทวีตที่โพสต์โดย Ilya Klimov (พนักงาน GitLab) เขียนเกี่ยวกับความน่าจะเป็นของลักษณะคล้ายคลึงกันสำหรับ Github Checks และ Status API: “ไม่น่าเป็นไปได้อย่างยิ่ง การตรวจสอบมีอยู่แล้วผ่าน API สถานะการคอมมิต และสำหรับสถานะ เรากำลังมุ่งมั่นที่จะเป็นระบบนิเวศแบบปิด”
ภาพหน้าจอของทวีตที่โพสต์โดย Ilya Klimov (พนักงาน GitLab) ที่เขียนเกี่ยวกับความน่าจะเป็นของลักษณะคล้ายคลึงกันสำหรับ Github Checks และ Status API (ตัวอย่างขนาดใหญ่)

วิธีหนึ่งในการตรวจสอบว่าคุณมีการสนับสนุนสำหรับคุณลักษณะเหล่านี้จริงหรือไม่: คุณสามารถค้นหาตัวแปรสภาพแวดล้อม GITLAB_FEATURES ในไปป์ไลน์ได้ หากไม่มี merge_request_performance_metrics และ metrics_reports ในรายการ แสดงว่าคุณลักษณะนี้ไม่ได้รับการสนับสนุน

 GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics, elastic_search, export_issues,group_bulk_edit,group_burndown_charts,group_webhooks, issuable_default_templates,issue_board_focus_mode,issue_weights,jenkins_integration, ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees, multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users, push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board, usage_quotas,visual_review_app,wip_limits

หากไม่มีการสนับสนุน เราต้องคิดอะไรบางอย่างขึ้นมา ตัวอย่างเช่น เราอาจเพิ่มความคิดเห็นในคำขอรวม แสดงความคิดเห็นกับตาราง ซึ่งประกอบด้วยข้อมูลทั้งหมดที่เราต้องการ เราสามารถปล่อยให้โค้ดของเราไม่ถูกแตะต้อง — สิ่งประดิษฐ์จะถูกสร้างขึ้น แต่วิดเจ็ตจะแสดงข้อความ «metrics are unchanged» เสมอ

พฤติกรรมที่แปลกและไม่ชัดเจนมาก ฉันต้องคิดให้รอบคอบเพื่อทำความเข้าใจกับสิ่งที่เกิดขึ้น

แล้วแผนล่ะ?

  1. เราต้องอ่านอาร์ติแฟกต์จาก master แบรนช์
  2. สร้างความคิดเห็นในรูปแบบมาร์ก markdown
  3. รับตัวระบุคำขอรวมจากสาขาคุณลักษณะปัจจุบันไปยังต้นแบบ
  4. เพิ่มความคิดเห็น

วิธีอ่านสิ่งประดิษฐ์จากสาขาปรมาจารย์

หากเราต้องการแสดงว่าเมตริกประสิทธิภาพเปลี่ยนไปอย่างไรระหว่าง master และสาขาคุณลักษณะ เราจำเป็นต้องอ่านสิ่งประดิษฐ์จาก master และในการทำเช่นนั้น เราจะต้องใช้ fetch

 npm i -S isomorphic-fetch
 // You can use predefined CI environment variables // @see https://gitlab.com/help/ci/variables/predefined_variables.md // We need fetch polyfill for node.js const fetch = require('isomorphic-fetch'); // GitLab domain const GITLAB_DOMAIN = process.env.CI_SERVER_HOST || process.env.GITLAB_DOMAIN || 'gitlab.com'; // User or organization name const NAME_SPACE = process.env.CI_PROJECT_NAMESPACE || process.env.PROJECT_NAMESPACE || 'silentimp'; // Repo name const PROJECT = process.env.CI_PROJECT_NAME || process.env.PROJECT_NAME || 'lighthouse-comments'; // Name of the job, which create an artifact const JOB_NAME = process.env.CI_JOB_NAME || process.env.JOB_NAME || 'lighthouse'; /* * Returns an artifact * * @param name {String} - artifact file name * @return {Object} - object with performance artifact * @throw {Error} - thhrow an error, if artifact contain string, that can't be parsed as a JSON. Or in case of fetch errors. */ const getArtifact = async name => { const response = await fetch(`https://${GITLAB_DOMAIN}/${NAME_SPACE}/${PROJECT}/-/jobs/artifacts/master/raw/${name}?job=${JOB_NAME}`); if (!response.ok) throw new Error('Artifact not found'); const data = await response.json(); return data; };

การสร้างข้อความแสดงความคิดเห็น

เราจำเป็นต้องสร้างข้อความแสดงความคิดเห็นในรูปแบบมาร์ก markdown มาสร้างฟังก์ชั่นการบริการที่จะช่วยเรา:

 /** * Return part of report for specific page * * @param report {Object} — report * @param subject {String} — subject, that allow find specific page * @return {Object} — page report */ const getPage = (report, subject) => report.find(item => (item.subject === subject)); /** * Return specific metric for the page * * @param page {Object} — page * @param name {String} — metrics name * @return {Object} — metric */ const getMetric = (page, name) => page.metrics.find(item => item.name === name); /** * Return table cell for desired metric * * @param branch {Object} - report from feature branch * @param master {Object} - report from master branch * @param name {String} - metrics name */ const buildCell = (branch, master, name) => { const branchMetric = getMetric(branch, name); const masterMetric = getMetric(master, name); const branchValue = branchMetric.value; const masterValue = masterMetric.value; const desiredLarger = branchMetric.desiredSize === 'larger'; const isChanged = branchValue !== masterValue; const larger = branchValue > masterValue; if (!isChanged) return `${branchValue}`; if (larger) return `${branchValue} ${desiredLarger ? '' : '' } **+${Math.abs(branchValue - masterValue).toFixed(2)}**`; return `${branchValue} ${!desiredLarger ? '' : '' } **-${Math.abs(branchValue - masterValue).toFixed(2)}**`; }; /** * Returns text of the comment with table inside * This table contain changes in all metrics * * @param branch {Object} report from feature branch * @param master {Object} report from master branch * @return {String} comment markdown */ const buildCommentText = (branch, master) =>{ const md = branch.map( page => { const pageAtMaster = getPage(master, page.subject); if (!pageAtMaster) return ''; const md = `|${page.subject}|${buildCell(page, pageAtMaster, 'Performance')}|${buildCell(page, pageAtMaster, 'Accessibility')}|${buildCell(page, pageAtMaster, 'Best Practices')}|${buildCell(page, pageAtMaster, 'SEO')}| `; return md; }).join(''); return ` |Path|Performance|Accessibility|Best Practices|SEO| |--- |--- |--- |--- |--- | ${md} `; };

สคริปต์ที่จะสร้างความคิดเห็น

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

สกรีนช็อตที่แสดงรูปแบบการสร้างโทเค็นและตัวเลือกเมนูที่ฉันได้กล่าวไว้ข้างต้น
สกรีนช็อตที่แสดงรูปแบบการสร้างโทเค็นและตัวเลือกเมนูที่ฉันได้กล่าวไว้ข้างต้น (ตัวอย่างขนาดใหญ่)

คุณจะต้องมี ID ของโครงการด้วย คุณสามารถค้นหาได้ในที่เก็บ 'การตั้งค่า' (ในเมนูย่อย 'ทั่วไป'):

ภาพหน้าจอแสดงหน้าการตั้งค่า ซึ่งคุณสามารถค้นหา Project ID
ภาพหน้าจอแสดงหน้าการตั้งค่า ซึ่งคุณสามารถค้นหารหัสโครงการได้ (ตัวอย่างขนาดใหญ่)

ในการเพิ่มความคิดเห็นในคำขอรวม เราจำเป็นต้องทราบ ID ฟังก์ชันที่ช่วยให้คุณได้รับ ID คำขอรวมมีลักษณะดังนี้:

 // You can set environment variables via CI/CD UI. // @see https://gitlab.com/help/ci/variables/README#variables // I have set GITLAB_TOKEN this way // ID of the project const GITLAB_PROJECT_ID = process.env.CI_PROJECT_ID || '18090019'; // Token const TOKEN = process.env.GITLAB_TOKEN; /** * Returns iid of the merge request from feature branch to master * @param from {String} — name of the feature branch * @param to {String} — name of the master branch * @return {Number} — iid of the merge request */ const getMRID = async (from, to) => { const response = await fetch(`https://${GITLAB_DOMAIN}/api/v4/projects/${GITLAB_PROJECT_ID}/merge_requests?target_branch=${to}&source_branch=${from}`, { method: 'GET', headers: { 'PRIVATE-TOKEN': TOKEN, } }); if (!response.ok) throw new Error('Merge request not found'); const [{iid}] = await response.json(); return iid; };

We need to get a feature branch name. You may use the environment variable CI_COMMIT_REF_SLUG inside the pipeline. Outside of the pipeline, you can use the current-git-branch package. Also, you will need to form a message body.

Let's install the packages we need for this matter:

 npm i -S current-git-branch form-data

And now, finally, function to add a comment:

 const FormData = require('form-data'); const branchName = require('current-git-branch'); // Branch from which we are making merge request // In the pipeline we have environment variable `CI_COMMIT_REF_NAME`, // which contains name of this banch. Function `branchName` // will return something like «HEAD detached» message in the pipeline. // And name of the branch outside of pipeline const CURRENT_BRANCH = process.env.CI_COMMIT_REF_NAME || branchName(); // Merge request target branch, usually it's master const DEFAULT_BRANCH = process.env.CI_DEFAULT_BRANCH || 'master'; /** * Adding comment to merege request * @param md {String} — markdown text of the comment */ const addComment = async md => { const iid = await getMRID(CURRENT_BRANCH, DEFAULT_BRANCH); const commentPath = `https://${GITLAB_DOMAIN}/api/v4/projects/${GITLAB_PROJECT_ID}/merge_requests/${iid}/notes`; const body = new FormData(); body.append('body', md); await fetch(commentPath, { method: 'POST', headers: { 'PRIVATE-TOKEN': TOKEN, }, body, }); };

And now we can generate and add a comment:

 cluster.on('message', (worker, msg) => { report = [...report, ...msg]; worker.disconnect(); reportsCount++; if (reportsCount === chunks.length) { fs.writeFileSync(`./performance.json`, JSON.stringify(report)); + if (CURRENT_BRANCH === DEFAULT_BRANCH) process.exit(0); + try { + const masterReport = await getArtifact('performance.json'); + const md = buildCommentText(report, masterReport) + await addComment(md); + } catch (error) { + console.log(error); + } process.exit(0); } });
  • Check the gist and demo repository.

Now create a merge request and you will get:

A screenshot of the merge request which shows comment with a table that contains a table with lighthouse metrics change
A screenshot of the merge request which shows comment with a table that contains a table with lighthouse metrics change. (ตัวอย่างขนาดใหญ่)

ประวัติย่อ

Comments are much less visible than widgets but it's still much better than nothing. This way we can visualize the performance even without artifacts.

การตรวจสอบสิทธิ์

OK, but what about authentication? The performance of the pages that require authentication is also important. It's easy: we will simply log in. puppeteer is essentially a fully-fledged browser and we can write scripts that mimic user actions:

 const LOGIN_URL = '/login'; const USER_EMAIL = process.env.USER_EMAIL; const USER_PASSWORD = process.env.USER_PASSWORD; /** * Authentication sctipt * @param browser {Object} — browser instance */ const login = async browser => { const page = await browser.newPage(); page.setCacheEnabled(false); await page.goto(`${DOMAIN}${LOGIN_URL}`, { waitUntil: 'networkidle2' }); await page.click('input[name=email]'); await page.keyboard.type(USER_EMAIL); await page.click('input[name=password]'); await page.keyboard.type(USER_PASSWORD); await page.click('button[data-test]', { waitUntil: 'domcontentloaded' }); };

Before checking a page that requires authentication, we may just run this script. เสร็จแล้ว.

สรุป

In this way, I built the performance monitoring system at Werkspot — a company I currently work for. It's great when you have the opportunity to experiment with the bleeding edge technology.

Now you also know how to visualize performance change, and it's sure to help you better track performance degradation. But what comes next? You can save the data and visualize it for a time period in order to better understand the big picture, and you can collect performance data directly from the users.

You may also check out a great talk on this subject: “Measuring Real User Performance In The Browser.” When you build the system that will collect performance data and visualize them, it will help to find your performance bottlenecks and resolve them. ขอให้โชคดีกับมัน!