[Android] มาทำความรู้จักกับโปรเจค Android ของเราให้มากขึ้นดีกว่า (part 2)

Kajornsak P.
5 min readMar 23, 2021

การทำระบบ build tracking เพื่อ monitor โปรเจคของเรา

Photo by Andrés Dallimonti on Unsplash

สืบเนื่องจากพาร์ทที่แล้ว นอกจากการไล่เช็ค dependency เพื่อ Modularization ที่ดีแล้ว เรายังต้องหาต่อด้วยว่า bottleneck ของการ build โปรเจคเรามันมาจากไหนกัน

ซึ่ง การที่จะรู้ได้ว่า bottleneck มาจากไหน เราต้องทำความเข้าใจกันก่อนว่า Gradle นั้น build โปรเจคเราแบบไหน เราถึงจะค่อย measure ทีหลังได้ว่าส่วนต่างๆใช้ระยะเวลาเท่าไหร่

Gradle tasks dependency

นอกจากโปรเจคเราจะมีระบบ dependency graph แล้ว ตัว Gradle เองก็มีการทำงานด้วยระบบ dependency graph เช่นกัน เวลาเราเรียกใช้ Gradle task หนึ่งที ตัว Gradle เองก็จะมีการคิดว่า task นั้นสามารถรันได้เลยรึเปล่า มี task อื่นๆที่เป็น dependency ด้วยหรือไม่ จากนั้น tasks graph ก็จะถูกสร้างขึ้นมาแล้วก็ส่งต่อให้ gradle daemon เป็นคนจัดการต่อ

ยกตัวอย่างเช่น ถ้าผมลองสั่ง build แอปเปล่าๆขึ้นมาด้วย :app:assembleDebug ดู จะมีอะไรเกิดขึ้นบ้าง (ภาพข้างล่างใช้ gradle-taskinfo ในการสร้าง tree ขึ้นมานะครับ ลองเล่นกันดูได้)

อันนี้เป็นแบบที่ simplified ที่สุดแล้ว

จะเห็นว่า assembleDebug เองก็มี dependency อยู่หลาย task เช่นกัน ได้แก่ compileDebugSources, mergeDebugNativeDebugMetadata และ packageDebug ซึ่งแต่ละอัน ก็มี dependency ซ้อนลงไปอีก!!

และถ้าเราลองดูลำดับการ execute gradle task แต่ละอันก็จะเป็นดังนี้

ก็คือมีการ execute ตามลำดับเดียวกับ Gradle tasks dependency graph ด้านบนเลย

ซึ่งการที่เรารู้ลำดับในการ execute Gradle tasks เนี่ย มันจะช่วยให้เราเข้าใจมากขึ้นว่าในแต่ละครั้งที่เรา build แอป หรือเรียก Gradle task ใดๆก็ตาม มันถูกเรียกใช้ยังไง และ เหนือไปกว่านั้น ถ้าเราสามารถรู้ระยะเวลาที่ถูกใช้ในแต่ละ task เราก็จะเริ่มมองเห็นแล้วว่า bottleneck นั้นอยู่ที่ไหน จากนั้นก็ค่อยๆแก้ไปทีละจุด

ณ ตอนนี้ เรารู้แล้วว่ากว่าที่เราจะได้แอปขึ้นมาหนึ่งอัน เพื่อ run ในเครื่องโทรศัพท์ของเรา Gradle นั้นทำอะไรกับโปรเจคของเราบ้าง สิ่งสุดท้ายที่เหลืออยู่ก็แค่การวัดระยะเวลาของแต่ละ task แล้วว่าเป็นอย่างไร เพราะเมื่อเรามี Measurement เราก็จะสามารถวิเคราะห์ได้โดยมี data ที่รองรับสมมติฐานในการแก้ปัญหานั่นเอง (Measurement เป็นอะไรที่สำคัญมากจริงๆนะ)

Gradle task tracking

โดยปกติแล้ว Gradle จะบอกอยู่แล้วว่าแต่ละ task ใช้เวลาเท่าไหร่ แต่อาจจะขาดๆไปบ้าง ซึ่งถ้าถามว่าเพียงพอต่อการนำไปใช้งานหรือไม่ คำตอบก็คือ ได้เหมือนกันครับ แต่ไม่ scalable อย่างมากๆ เพราะถ้าเรามีจำนวน task หลักร้อยหรือหลักพัน ก็คงยากที่จะมาไล่ดูทีละอัน และถ้ามีเครื่องคอมของ developer ซัก 10คน 100คน ก็จะยากที่จะเก็บข้อมูลเหล่านี้ออกมาอีก

ถ้าต้องมานั่งก็อปทีละอันก็จะลำบากเกินไป~

วันนี้ผมเลยมีตัวช่วยสำคัญ ที่เราจะเอามาใช้ในการ Measure ในส่วนของ Gradle task มาแนะนำกันน เรียกได้ว่าใช้ได้ตั้งแต่ scale 1 คน จนเป็นทั้งองค์กร ก็ยังไหว

Introducing Talaiot

ซึ่งตัวช่วยก็คือ Talaiot นั่นเองง เป็น Gradle plugin จากคุณ Iñaki Villar ที่เรียกว่าเป็นคนจุดประกายให้ผมหันมาสนใจเรื่อง Build system ของแอนดรอยด์เลยก็ว่าได้

ซึ่งเจ้า plugin นี้หลักๆจะทำหน้าที่เป็น Listener ที่เก็บข้อมูลจากตัว Lifecycle ของ Gradle และทำหน้าที่เป็น Publisher ที่คอยส่งข้อมูลให้เรานำไปเก็บและเอามาใช้ได้ในภายหลัง

ซึ่งเราสามารถเลือก plugin ต่างๆได้ตามใจชอบเลย ขึ้นอยู่กับว่าเราออกแบบ system ไว้แบบไหน

ซึ่งวิธีการใช้งานก็ง่ายมากๆ ตามขั้นตอนใน Github ได้เลย ขึ้นอยู่กับโปรเจคของเราว่า setup แบบไหนไว้อยู่ โดยในตัวอย่างนี้ผมจะใช้กับ gradle config แบบปกติที่ได้มาจากการสร้างโปรเจคใหม่เลย ในที่นี้ผมจะใช้ควบคู่กับ InfluxDB เป็นที่เก็บข้อมูลอีกที

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.cdsap.talaiot' version '1.4.2'
}
//build.gradle ที่โมดูล app

จากนั้นก็ทำการ config ตัว plugin ได้เลย

talaiot {
publishers {
influxDbPublisher {
dbName = "tracking"
url = "http://localhost:8086"
}
}
}
//build.gradle ที่โมดูล app

โดยที่ Publisher จะขึ้นอยู่กับตัว Consumer ที่ใช้ เพียงแค่ตั้งค่าให้ถูกต้องเท่านั้นก็ใช้ได้เลย ซึ่งในที่นี้ผมมี influxDB อยู่ในเครื่องอยู่แล้ว ก็สามารถชี้ไปยัง url ของ InfluxDB ได้เลย

จริงๆแล้วเราจะใช้ตัว Consumer เป็นอะไรก็ได้เช่นกัน ที่ผมใช้ InfluxDB เพราะรู้สึกว่าคล่องมือมากกว่า แถม setup ก็ง่าย 😂 จากนั้นเราก็ลอง build แอปดูได้เลย หรือรัน gradle task ไหนๆก็ได้เช่นกัน

ถ้าเปิดดูข้อมูลด้วย Chronograf ก็จะเห็นว่าถูกส่งมาจาก publisher เรียบร้อย

แต่ข้อมูลที่เก็บมาเนี่ย มันค่อนข้างจะเป็นอะไรที่ดูยากพอสมควรเลย ทางที่ดี เราควรจะหา Visualization tool ที่เหมาะสมด้วยเช่นกัน ซึ่งก็คงจะหนีไม่พ้นใครนอกจาก Grafana (จริงๆ Chronograf ก็ใช้ได้นะ แต่ Grafana จะมีความ flexible มากกว่าในด้านของ datasource ที่รองรับ)

ซึ่งถ้าใครที่ setup ทุกอย่างไว้อยู่แล้วตั้งแต่แรก ใน Repo ของ Talaiot เองก็มี dashboard มาให้แล้วเช่นกัน ไม่ต้องมาสร้างเองง สามารถ import เข้า grafana ได้เลย

https://github.com/cdsap/Talaiot/blob/master/docker/grafana/dashboards/talaiot.json

เพียงแค่ Import เข้ามาแล้วเลือก db ให้ตรงกับของเรา ก็ใช้ได้เลย

แต่!!! ถ้าใครยังไม่มีทั้ง InfluxDB และ Grafana ลงไว้ในเครื่อง คุณ Iñaki ก็ได้เตรียม Docker image ไว้ให้ด้วยเหมือนกัน สามารถเข้าไปดาวน์โหลดได้จาก Repo นี้เลย

docker run -d \
-p 3003:3003 \
-p 3004:8083 \
-p 8086:8086 \
-p 22022:22 \
-v /var/lib/influxdb \
-v /var/lib/grafana \
cdsap/talaiot:latest

เราก็จะได้ทั้ง InfluxDB และ Grafana ที่มี pre-imported dashboards ให้เรียบร้อยแล้วว

นอกจากนั้น เรายังสามารถปรับตัว Talaiot ให้ส่งข้อมูลเฉพาะบางอย่างที่เราสนใจก็ได้นะ เพียงแค่เพิ่ม option ต่างๆใน config ที่ gradle extension ได้เลย

ไม่ว่าจะเป็นการ opt-out การ tracking จาก CI Machine เพราะบางทีเราอาจจะต้องการเก็บแค่จาก developer machine อย่างเดียว ก็สามารถระบุไปได้เลย ตามนี้

talaiot {
ignoreWhen {
envName = "CI" //แล้วบน CI เราก็ inject env variable เข้ามา
envValue = "true"
}
}

ซึ่งสามารถดูใน Documentation ได้เลย เค้าเขียนไว้ละเอียดมากก

จากการเก็บข้อมูล Gradle tasks ที่ผ่านมาและการ Visualization นั้นบอกอะไรเราบ้างล่ะ?

เมื่อเรามี measurement ที่ดีแล้ว คราวนี้เราก็จะมีข้อมูลซัพพอร์ทสมมติฐานต่างๆได้แล้ว ว่าทำไมโปรเจคของเราถึง build ได้ช้า มีส่วนไหนที่เป็นปัญหาบ้าง ยกตัวอย่างเช่น ผมมี Machine อยู่ 2 เครื่อง ทำการสั่ง build เหมือนๆกัน หลายๆครั้ง ก็จะเห็นว่าได้ค่าที่แตกต่างกันตามตารางด้านล่าง

จะพอเห็นได้ว่า มีปัจจัยอะไรบ้างที่แตกต่างกันระหว่างสองเครื่องนี้

  1. Gradle version
  2. Java VM Name
  3. Username….(ไม่นับ!!)

ซึ่งเรายังบอกไม่ได้เช่นกันว่าอะไรทำให้แตกต่างจริงๆ งั้นลองมาดูแบบเทียบ 1–1 ให้มากขึ้นซิ

สองอันนี้คือการสั่ง assemble ด้วยสถานะที่ cache ทุกอย่างเรียบร้อยแล้ว (ก็คือแทบไม่มีการ re-compile code เลยเพราะโค้ดไม่เปลี่ยน)

ซึ่งเครื่องบนคือ Macbook Pro ปี 2016 ที่ใช้ JDK เวอร์ชั่น 1.8 จาก OpenJDK บน Gradle 6.8.1 และในทางกลับกัน เครื่องล่างคือ Mac Mini M1 ปี 2020 ที่ใช้ Zulu JDK เวอร์ชั่น 11 บน Gradle 6.8.2

ก็ยังบอกไม่ได้อีกว่าอะไรดีกว่ากัน!!

นั่นเป็นเพราะเราไม่มี control variable ในการทดสอบนั่นเอง ซึ่งผมเองก็แอบขี้เกียจในการ set environment ให้เหมือนๆกันด้วยแหละ 🥺 แต่คิดว่าทุกคนที่กำลังอ่านอยู่น่าจะพอเห็นภาพเหมือนกันแล้วใช่มั้ยย ว่าเราสามารถนำข้อมูลเหล่านี้ไปวิเคราะห์ต่อยอดอย่างไรได้บ้าง 😆

ลองดูแบบมี control variable ละกัน

ลองเปลี่ยนแค่ Gradle Version จาก 6.8.1 เป็น 6.8.2 ดู…ถามว่าสามารถสรุปได้รึเปล่าว่ามันเร็วขึ้น เมื่อเทียบจากข้อมูลคู่นี้

ไม่เสมอไป!! อ่าว….

เหตุผลที่ไม่สามารถสรุปได้ทันทีว่า Gradle 6.8.2 นั้นเร็วกว่า 6.8.1 อยู่ 0.14 วินาที เพราะเราทำการเทียบแค่ครั้งเดียวครับ ซึ่งถ้าสลับกันรันไปเรื่อยๆ 100 ครั้ง เราอาจจะได้ผลที่แตกต่างกันก็เป็นได้ ขึ้นอยู่กับสภาพของ Machine ณ ตอนนั้นด้วย

ซึ่งตามหลักสถิติแล้ว เราควรจะเทียบในหลายๆ percentile ด้วย โดยอันนี้จะอยู่ที่เราและ ว่าอยากดูที่ระดับไหนบ้าง อาจจะเช็คคร่าวๆที่ P50, P90 (ถ้าอยากดีเทลมากๆก็ P95) ไปเลย ซึ่งจำนวนข้อมูลที่เรามีก็ต้องสอดคล้องเช่นกัน อาจจะต้องมา weight กับขนาดของทีมอีกทีนึง

พอจะเห็นภาพกันมากขึ้นแล้วใช่มั้ยครับ ว่าเราสามารถนำ Measurement + Visualization มาวิเคราะห์โปรเจคเราอย่างไรได้บ้าง

ซึ่งด้านบนที่กล่าวมาเนี่ย เป็นแค่การดู Metric Build อย่างเดียวจาก Talaiot ซึ่งจะบอกเราเกี่ยวกับข้อมูลของการ build ในแต่ละครั้ง ไม่ว่าจะเป็น HW, Gradle Version, JVM Version, Configuration ต่างๆ ซึ่งยังมีอีกหลายอย่างให้นำมาดูได้ สามารถดูได้จากใน Repo ของ Talaiot เลยครับ วาร์ป

ลองมาดูอีก Metric นึงกันดีกว่า

สมมติว่าผมมีโจทย์ที่ต้องการแก้อันนึง ก็คือ “แอปเราใช้เวลา assemble เท่าไหร่” โจทย์ง่ายๆเลย ผมสามารถนำข้อมูลที่มี + Grafana มาสร้าง dashboard ยังไงได้บ้างนะ

Step 1 : สร้าง Panel Table มาก่อนเลยอันนึง

จากนั้นผมลอง query จาก measurement task ใน rpTalaiot ดู (rpTalaiot คือ default retention policy ของ InfluxDB publisher ที่ใช้ ลองศึกษาเพิ่มในส่วนของ InfluxDB ได้ที่นี่ครับ วาร์ป)

โดยผมจะเลือกเฉพาะ task :app:assemble เท่านั้น โดยเอาเฉพาะ mean มาดู

อืมมม ดูดีแหละ…แต่นั่นมันเลขอะไรกัน!!

โดย default แล้ว unit ของ task แต่ละอันจะไม่ใช่หน่วยวินาทีครับ แต่เป็นหน่วย millisecond (ms) เพราะฉะนั้นเราต้องมา format กันที่หน้างานซะหน่อย ผ่าน Column Style ของ Grafana

โดยเพิ่ม style ใหม่ให้กับ column ที่ชื่อ duration (มาจาก alias ของ query ด้านบน)

ออกมาดูเป็นผู้เป็นคนขึ้นเยอะ!! แต่เนื่องจากเราไม่ได้ group ว่าจะ group by อะไร ทำให้เราเห็นเป็นค่า mean ของ :app:assemble ทั้งหมดนั่นเอง แปลว่าเราควรใช้ panel แบบอื่นที่ดีกว่านี้!! ซึ่งในเคสนี้ใช้ Singlestat น่าจะเหมาะกว่า โดยเลือก threshold ตามขนาดโปรเจคเราได้เลย (ค่อยๆจูนกันไปก็ได้ระหว่างเก็บข้อมูล)

อ้ะ ดูดีขึ้นมาหน่อย

ลองเพิ่มอีกอันนึงเพื่อดูค่าเฉลี่ยของ task ที่สำคัญๆละกัน เช่น assemble, build, compileDebugKotlin และอื่นๆตามใจชอบเลยย คราวนี้เราก็จะรู้แล้วว่า task ไหนนั้นกินเวลานาน หรือมาจาก module ไหนกันที่ compile นานกว่าเพื่อนเค้า เราก็จะแก้ปัญหาเป็นจุดๆได้ (อาจจะแตกโมดูลเพิ่ม หรือ optimize อื่นๆก็ว่ากันไปตามเคส)

ซึ่งพอได้ 2 panel ที่รู้สึกพอใจแล้ว ก็ทำการเซฟได้เลย Grafana ก็จะเก็บ panel เหล่านี้ไว้ใน dashboard ให้ (หรือจะ export ไปให้คนอื่น หรือเก็บไว้ที่อื่นเป็น JSON ก็ได้นะ)

สวยงามม~~

ถ้าใครมี 2 panel นี้แล้ว แนะนำให้ลองเล่นกับ Talaiot และ Grafana ดูเลยครับ จะเห็นได้เลยว่ามันช่วยให้เราเห็นภาพปัญหาต่างๆของโปรเจคเราได้จริงๆ หรือถ้าเราอยากโชว์ข้อมูลอะไรออกมา ก็ visualize ได้ง่ายๆเลย (ยิ่งมีข้อมูลในทีมเยอะ ยิ่งรู้ปัญหาของโปรเจคเราได้ดีมากขึ้น!)

โค้ดของ dashboard ผมที่เพิ่มขึ้นมาอีก 2 panel ก็จะเป็นประมาณนี้ (วาร์ป)

มาถึงตรงนี้แล้ว หลังจากที่เราเข้าใจทั้งการ Modularization ของโปรเจคเราแล้ว และวิธีการทำงานของเจ้าตัว Gradle เองแล้ว พอเราหา bottleneck ที่เกิดขึ้นได้ เราก็จะเริ่มลด scope ของสิ่งที่ต้องแก้ให้กับโปรเจคเรา แต่ถ้ามันยังไม่สุดล่ะ ยังรู้สึกว่ายังเร็วกว่านี้ได้อีก!! ทีนี้เราต้องมาว่ากันด้วยเรื่องของการ Optimization แล้วล่ะ ว่าจะรีดความสามารถของ Machine และ Build system ได้ยังไง ซึ่งติดตามกันได้ในตอนต่อไปปป~~~

มาทำความรู้จักกับโปรเจค Android ของเราให้มากขึ้นดีกว่า (part 1)

มาทำความรู้จักกับโปรเจค Android ของเราให้มากขึ้นดีกว่า (part 2) (อันนี้)

มาทำความรู้จักกับโปรเจค Android ของเราให้มากขึ้นดีกว่า (part 3) (เร็วๆนี้)

--

--

Kajornsak P.

Android & iOS developer. Interest in UI/UX design. Currently, Senior iOS Engineer at Agoda — Mobile Platform team