क्लाइंट ऐप्लिकेशन और फ़्रंटएंड वेब डेवलपर, अपने कोड की परफ़ॉर्मेंस को बेहतर बनाने के लिए, आम तौर पर Android Studio CPU Profiler या Chrome में शामिल प्रोफ़ाइलिंग टूल जैसे टूल का इस्तेमाल करते हैं. हालांकि, बैकएंड सेवाओं पर काम करने वाले लोगों के लिए, इस तरह की तकनीकें उतनी आसानी से उपलब्ध नहीं हैं और न ही वे इनका इस्तेमाल करते हैं. Stackdriver Profiler, सेवा डेवलप करने वालों को ये सुविधाएं उपलब्ध कराता है. इससे कोई फ़र्क़ नहीं पड़ता कि उनका कोड Google Cloud Platform पर चल रहा है या किसी और प्लैटफ़ॉर्म पर.

यह टूल, आपके प्रोडक्शन ऐप्लिकेशन से सीपीयू के इस्तेमाल और मेमोरी-अलॉटमेंट की जानकारी इकट्ठा करता है. यह जानकारी को ऐप्लिकेशन के सोर्स कोड से जोड़ता है. इससे आपको यह पता चलता है कि ऐप्लिकेशन के कौनसे हिस्से सबसे ज़्यादा संसाधनों का इस्तेमाल कर रहे हैं. साथ ही, इससे कोड की परफ़ॉर्मेंस की विशेषताओं के बारे में भी पता चलता है. इस टूल में डेटा इकट्ठा करने की तकनीकों का इस्तेमाल किया जाता है. इससे, प्रोडक्शन एनवायरमेंट में लगातार इसका इस्तेमाल करना आसान हो जाता है.
इस कोडलैब में, आपको Go प्रोग्राम के लिए Stackdriver Profiler सेट अप करने का तरीका बताया जाएगा. साथ ही, आपको यह भी पता चलेगा कि यह टूल, ऐप्लिकेशन की परफ़ॉर्मेंस के बारे में किस तरह की अहम जानकारी दे सकता है.
आपको क्या सीखने को मिलेगा
- Stackdriver Profiler की मदद से प्रोफ़ाइलिंग के लिए, Go प्रोग्राम को कॉन्फ़िगर करने का तरीका.
- Stackdriver Profiler की मदद से, परफ़ॉर्मेंस का डेटा इकट्ठा करने, देखने, और उसका विश्लेषण करने का तरीका.
आपको इन चीज़ों की ज़रूरत होगी
- Google Cloud Platform प्रोजेक्ट
- कोई ब्राउज़र, जैसे कि Chrome या Firefox
- Vim, EMACs या Nano जैसे स्टैंडर्ड Linux टेक्स्ट एडिटर के बारे में जानकारी होना
इस ट्यूटोरियल का इस्तेमाल कैसे किया जाएगा?
Google Cloud Platform इस्तेमाल करने के अपने अनुभव को आप क्या रेटिंग देंगे?
अपने हिसाब से एनवायरमेंट सेट अप करना
अगर आपके पास पहले से कोई Google खाता (Gmail या Google Apps) नहीं है, तो आपको एक खाता बनाना होगा. Google Cloud Platform Console (console.cloud.google.com) में साइन इन करें और एक नया प्रोजेक्ट बनाएं:
प्रोजेक्ट आईडी याद रखें. यह सभी Google Cloud प्रोजेक्ट के लिए एक यूनीक नाम होता है. ऊपर दिया गया नाम पहले ही इस्तेमाल किया जा चुका है. इसलिए, यह आपके लिए काम नहीं करेगा. माफ़ करें! इस कोड लैब में इसे बाद में PROJECT_ID के तौर पर दिखाया जाएगा.
इसके बाद, Google Cloud संसाधनों का इस्तेमाल करने के लिए, आपको Cloud Console में बिलिंग चालू करनी होगी.
इस कोडलैब को पूरा करने में आपको कुछ डॉलर से ज़्यादा खर्च नहीं करने पड़ेंगे. हालांकि, अगर आपको ज़्यादा संसाधनों का इस्तेमाल करना है या उन्हें चालू रखना है, तो यह खर्च बढ़ सकता है. इस दस्तावेज़ के आखिर में "सफ़ाई" सेक्शन देखें.
Google Cloud Platform के नए उपयोगकर्ता, 300 डॉलर के क्रेडिट के साथ मुफ़्त में आज़माने की सुविधा पा सकते हैं.
Google Cloud Shell
Google Cloud को अपने लैपटॉप से रिमोटली ऐक्सेस किया जा सकता है. हालांकि, इस कोडलैब में सेटअप को आसान बनाने के लिए, हम Google Cloud Shell का इस्तेमाल करेंगे. यह क्लाउड में चलने वाला कमांड लाइन एनवायरमेंट है.
Google Cloud Shell चालू करना
GCP Console में, सबसे ऊपर दाईं ओर मौजूद टूलबार पर मौजूद Cloud Shell आइकॉन पर क्लिक करें:
इसके बाद, "Cloud Shell शुरू करें" पर क्लिक करें:
इसे चालू करने और एनवायरमेंट से कनेक्ट करने में सिर्फ़ कुछ सेकंड लगेंगे:
इस वर्चुअल मशीन में, डेवलपमेंट के लिए ज़रूरी सभी टूल पहले से मौजूद हैं. यह 5 जीबी की होम डायरेक्ट्री उपलब्ध कराता है. साथ ही, Google Cloud पर काम करता है. इससे नेटवर्क की परफ़ॉर्मेंस और पुष्टि करने की प्रोसेस बेहतर होती है. इस लैब में ज़्यादातर काम, सिर्फ़ ब्राउज़र या Google Chromebook से किया जा सकता है.
Cloud Shell से कनेक्ट होने के बाद, आपको दिखेगा कि आपकी पुष्टि पहले ही हो चुकी है और प्रोजेक्ट को आपके PROJECT_ID पर पहले ही सेट कर दिया गया है.
पुष्टि करने के लिए कि आपने पुष्टि कर ली है, Cloud Shell में यह कमांड चलाएं:
gcloud auth list
कमांड आउटपुट
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
कमांड आउटपुट
[core] project = <PROJECT_ID>
अगर ऐसा नहीं है, तो इस कमांड का इस्तेमाल करके इसे सेट किया जा सकता है:
gcloud config set project <PROJECT_ID>
कमांड आउटपुट
Updated property [core/project].
Cloud Console में, बाईं ओर मौजूद नेविगेशन बार में "Profiler" पर क्लिक करके, Profiler के यूज़र इंटरफ़ेस (यूआई) पर जाएं:

इसके अलावा, Cloud Console के खोज बार का इस्तेमाल करके, Profiler के यूज़र इंटरफ़ेस (यूआई) पर भी जाया जा सकता है. इसके लिए, "Stackdriver Profiler" टाइप करें और खोज के नतीजों में मिले आइटम को चुनें. दोनों ही मामलों में, आपको नीचे दिए गए उदाहरण की तरह, "दिखाने के लिए कोई डेटा नहीं है" मैसेज के साथ Profiler का यूज़र इंटरफ़ेस (यूआई) दिखेगा. यह प्रोजेक्ट नया है. इसलिए, इसमें अब तक कोई प्रोफ़ाइलिंग डेटा इकट्ठा नहीं किया गया है.

अब किसी आइटम की प्रोफ़ाइल बनाने का समय आ गया है!
हम Github पर उपलब्ध एक सामान्य सिंथेटिक Go ऐप्लिकेशन का इस्तेमाल करेंगे. Cloud Shell टर्मिनल में, जो अब भी खुला है (और जब Profiler के यूज़र इंटरफ़ेस में "दिखाने के लिए कोई डेटा नहीं है" मैसेज अब भी दिख रहा हो), यह कमांड चलाएं:
$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...
इसके बाद, ऐप्लिकेशन डायरेक्ट्री पर जाएं:
$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp
इस डायरेक्ट्री में "main.go" फ़ाइल मौजूद है. यह एक सिंथेटिक ऐप्लिकेशन है, जिसमें प्रोफ़ाइलिंग एजेंट चालू है:
main.go
...
import (
...
"cloud.google.com/go/profiler"
)
...
func main() {
err := profiler.Start(profiler.Config{
Service: "hotapp-service",
DebugLogging: true,
MutexProfiling: true,
})
if err != nil {
log.Fatalf("failed to start the profiler: %v", err)
}
...
}प्रोफ़ाइलिंग एजेंट, डिफ़ॉल्ट रूप से सीपीयू, हीप, और थ्रेड प्रोफ़ाइल इकट्ठा करता है. यहां दिया गया कोड, म्यूटेक्स (इसे "कंटेंशन" भी कहा जाता है) प्रोफ़ाइलें इकट्ठा करने की सुविधा चालू करता है.
अब प्रोग्राम चलाएं:
$ go run main.go
प्रोग्राम के चलने के दौरान, प्रोफ़ाइलिंग एजेंट, कॉन्फ़िगर किए गए पांच टाइप की प्रोफ़ाइलें समय-समय पर इकट्ठा करेगा. समय के साथ कलेक्शन को रैंडमाइज़ किया जाता है. हर टाइप के लिए, हर मिनट में एक प्रोफ़ाइल की औसत दर होती है. इसलिए, हर टाइप को इकट्ठा करने में तीन मिनट तक लग सकते हैं. प्रोग्राम, प्रोफ़ाइल बनाने के दौरान आपको इसकी सूचना देता है. ऊपर दिए गए कॉन्फ़िगरेशन में, DebugLogging फ़्लैग की मदद से मैसेज चालू किए जाते हैं. ऐसा न होने पर, एजेंट चुपचाप काम करता है:
$ go run main.go 2018/03/28 15:10:24 profiler has started 2018/03/28 15:10:57 successfully created profile THREADS 2018/03/28 15:10:57 start uploading profile 2018/03/28 15:11:19 successfully created profile CONTENTION 2018/03/28 15:11:30 start uploading profile 2018/03/28 15:11:40 successfully created profile CPU 2018/03/28 15:11:51 start uploading profile 2018/03/28 15:11:53 successfully created profile CONTENTION 2018/03/28 15:12:03 start uploading profile 2018/03/28 15:12:04 successfully created profile HEAP 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:04 successfully created profile THREADS 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:25 successfully created profile HEAP 2018/03/28 15:12:25 start uploading profile 2018/03/28 15:12:37 successfully created profile CPU ...
पहली प्रोफ़ाइल इकट्ठा होने के बाद, यूज़र इंटरफ़ेस (यूआई) अपने-आप अपडेट हो जाएगा. इसके बाद, यह अपने-आप अपडेट नहीं होगा. इसलिए, नया डेटा देखने के लिए, आपको Profiler के यूज़र इंटरफ़ेस (यूआई) को मैन्युअल तरीके से रीफ़्रेश करना होगा. इसके लिए, टाइम इंटरवल पिकर में मौजूद 'अभी' बटन पर दो बार क्लिक करें:

यूज़र इंटरफ़ेस रीफ़्रेश होने के बाद, आपको कुछ ऐसा दिखेगा:

प्रोफ़ाइल टाइप चुनने वाले टूल में, पांच तरह की प्रोफ़ाइलें दिखती हैं:

अब हम हर तरह की प्रोफ़ाइल और यूज़र इंटरफ़ेस (यूआई) की कुछ अहम क्षमताओं की समीक्षा करेंगे. इसके बाद, कुछ एक्सपेरिमेंट करेंगे. इस चरण में, आपको Cloud Shell टर्मिनल की ज़रूरत नहीं है. इसलिए, CTRL-C दबाकर और "exit" टाइप करके इसे बंद किया जा सकता है.
अब हमने कुछ डेटा इकट्ठा कर लिया है. आइए, इस पर ज़्यादा बारीकी से नज़र डालें. हम एक सिंथेटिक ऐप्लिकेशन का इस्तेमाल कर रहे हैं. इसका सोर्स Github पर उपलब्ध है. यह ऐप्लिकेशन, प्रोडक्शन में परफ़ॉर्मेंस से जुड़ी अलग-अलग समस्याओं के सामान्य व्यवहारों को सिम्युलेट करता है.
सीपीयू का ज़्यादा इस्तेमाल करने वाला कोड
सीपीयू प्रोफ़ाइल का टाइप चुनें. यूआई के लोड होने के बाद, आपको फ़्लेम ग्राफ़ में load फ़ंक्शन के लिए चार लीफ़ ब्लॉक दिखेंगे. ये सभी ब्लॉक, सीपीयू के इस्तेमाल के लिए ज़िम्मेदार होते हैं:

इस फ़ंक्शन को खास तौर पर इसलिए लिखा गया है, ताकि यह टाइट लूप चलाकर ज़्यादा से ज़्यादा सीपीयू साइकल का इस्तेमाल कर सके:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}इस फ़ंक्शन को busyloop() से चार कॉल पाथ के ज़रिए, परोक्ष रूप से कॉल किया जाता है: busyloop → {foo1, foo2} → {bar, baz} → load. फ़ंक्शन बॉक्स की चौड़ाई, कॉल पाथ की तुलनात्मक लागत दिखाती है. इस मामले में, चारों पाथ की लागत लगभग एक जैसी है. किसी प्रोग्राम में, आपको उन कॉल पाथ को ऑप्टिमाइज़ करने पर फ़ोकस करना चाहिए जो परफ़ॉर्मेंस के हिसाब से सबसे ज़्यादा अहम हैं. फ़्लेम ग्राफ़ में, ज़्यादा समय लेने वाले पाथ को बड़े बॉक्स के साथ विज़ुअलाइज़ किया जाता है. इससे इन पाथ की पहचान करना आसान हो जाता है.
डेटा को और बेहतर तरीके से दिखाने के लिए, प्रोफ़ाइल डेटा फ़िल्टर का इस्तेमाल किया जा सकता है. उदाहरण के लिए, "baz" को फ़िल्टर स्ट्रिंग के तौर पर तय करके, "स्टैक दिखाएं" फ़िल्टर जोड़ने की कोशिश करें. आपको नीचे दिए गए स्क्रीनशॉट जैसा कुछ दिखेगा. इसमें load() पर कॉल करने के चार रास्तों में से सिर्फ़ दो दिख रहे हैं. ये दोनों ऐसे पाथ हैं जो ऐसे फ़ंक्शन से होकर गुज़रते हैं जिनके नाम में "baz" स्ट्रिंग मौजूद है. इस तरह से फ़िल्टर करने की सुविधा तब काम आती है, जब आपको किसी बड़े प्रोग्राम के किसी छोटे हिस्से पर फ़ोकस करना हो. उदाहरण के लिए, ऐसा इसलिए हो सकता है, क्योंकि आपके पास उस प्रोग्राम का सिर्फ़ एक हिस्सा है.

मेमोरी का ज़्यादा इस्तेमाल करने वाला कोड
अब "Heap" प्रोफ़ाइल टाइप पर स्विच करें. पक्का करें कि आपने पिछले एक्सपेरिमेंट में बनाए गए सभी फ़िल्टर हटा दिए हों. अब आपको एक फ़्लेम ग्राफ़ दिखेगा. इसमें allocImpl को alloc ने कॉल किया है. साथ ही, इसे ऐप्लिकेशन में मेमोरी का मुख्य इस्तेमाल करने वाले फ़ंक्शन के तौर पर दिखाया गया है:

फ़्लेम ग्राफ़ के ऊपर मौजूद खास जानकारी वाली टेबल से पता चलता है कि ऐप्लिकेशन में इस्तेमाल की गई कुल मेमोरी का औसत ~57.4 MiB है. इसमें से ज़्यादातर मेमोरी, allocImpl फ़ंक्शन ने इस्तेमाल की है. इस फ़ंक्शन को लागू करने के तरीके को देखते हुए, इसमें कोई हैरानी नहीं है:
main.go
func allocImpl() {
// Allocate 64 MiB in 64 KiB chunks
for i := 0; i < 64*16; i++ {
mem = append(mem, make([]byte, 64*1024))
}
}यह फ़ंक्शन एक बार चलता है. यह 64 MiB को छोटे-छोटे हिस्सों में बांटता है. इसके बाद, उन हिस्सों के पॉइंटर को ग्लोबल वैरिएबल में सेव करता है, ताकि उन्हें गार्बेज कलेक्शन से बचाया जा सके. ध्यान दें कि प्रोफ़ाइलर के इस्तेमाल की गई मेमोरी की मात्रा, 64 MiB से थोड़ी अलग होती है: Go हीप प्रोफ़ाइलर एक स्टैटिस्टिकल टूल है. इसलिए, मेज़रमेंट में कम ओवरहेड होता है, लेकिन यह बाइट के हिसाब से सटीक नहीं होता. इस तरह का ~10% अंतर दिखने पर हैरान न हों.
IO-इंटेंसिव कोड
प्रोफ़ाइल टाइप चुनने वाले टूल में "Threads" चुनने पर, डिसप्ले फ़्लेम ग्राफ़ पर स्विच हो जाएगा. इसमें ज़्यादातर चौड़ाई wait और waitImpl फ़ंक्शन ने ली है:

फ़्लेम ग्राफ़ के ऊपर दी गई खास जानकारी में, यह देखा जा सकता है कि 100 गोरूटीन ऐसे हैं जिनका कॉल स्टैक, wait फ़ंक्शन से बढ़ता है. यह बिलकुल सही है, क्योंकि इंतज़ार की इस प्रोसेस को शुरू करने वाला कोड ऐसा दिखता है:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}इस प्रोफ़ाइल टाइप से यह समझने में मदद मिलती है कि प्रोग्राम, इंतज़ार में (जैसे कि I/O) कितना समय बिताता है. ऐसे कॉल स्टैक को आम तौर पर सीपीयू प्रोफ़ाइलर से सैंपल नहीं किया जाता, क्योंकि ये सीपीयू के समय का कोई खास हिस्सा इस्तेमाल नहीं करते. आपको अक्सर थ्रेड प्रोफ़ाइलों के साथ "स्टैक छिपाएं" फ़िल्टर का इस्तेमाल करना होगा. उदाहरण के लिए, gopark, को कॉल करने वाले सभी स्टैक को छिपाने के लिए, क्योंकि वे अक्सर आइडल गोरूटीन होते हैं और I/O पर इंतज़ार करने वाले स्टैक की तुलना में कम दिलचस्प होते हैं.
थ्रेड प्रोफ़ाइल टाइप से, प्रोग्राम में उन पॉइंट की पहचान करने में भी मदद मिल सकती है जहां थ्रेड, प्रोग्राम के किसी दूसरे हिस्से के मालिकाना हक वाले म्यूटेक्स के लिए लंबे समय तक इंतज़ार कर रही हैं. हालांकि, इसके लिए नीचे दिया गया प्रोफ़ाइल टाइप ज़्यादा फ़ायदेमंद है.
Contention-intensive Code
Contention प्रोफ़ाइल टाइप से, प्रोग्राम में सबसे ज़्यादा "ज़रूरी" लॉक की पहचान होती है. यह प्रोफ़ाइल टाइप, Go प्रोग्राम के लिए उपलब्ध है. हालांकि, इसे साफ़ तौर पर चालू करना होगा. इसके लिए, एजेंट कॉन्फ़िगरेशन कोड में "MutexProfiling: true" डालना होगा. यह कलेक्शन, "Contentions" मेट्रिक के तहत काम करता है. इसमें यह रिकॉर्ड किया जाता है कि किसी लॉक को goroutine A से अनलॉक किए जाने के दौरान, goroutine B को कितनी बार लॉक के अनलॉक होने का इंतज़ार करना पड़ा. यह "Delay" मेट्रिक के तहत, उस समय को भी रिकॉर्ड करता है जब ब्लॉक की गई goroutine ने लॉक के लिए इंतज़ार किया था. इस उदाहरण में, एक ही कंटेंशन स्टैक है और लॉक के लिए इंतज़ार करने का कुल समय 11.03 सेकंड था:

इस प्रोफ़ाइल को जनरेट करने वाले कोड में, चार गोरूटीन होते हैं. ये सभी एक म्यूटेक्स के लिए आपस में प्रतिस्पर्धा करते हैं:
main.go
func contention(d time.Duration) {
contentionImpl(d)
}
func contentionImpl(d time.Duration) {
for {
mu.Lock()
time.Sleep(d)
mu.Unlock()
}
}
...
func main() {
...
for i := 0; i < 4; i++ {
go contention(time.Duration(i) * 50 * time.Millisecond)
}
}इस लैब में, आपने सीखा कि Stackdriver Profiler के साथ इस्तेमाल करने के लिए, Go प्रोग्राम को कैसे कॉन्फ़िगर किया जा सकता है. आपने यह भी सीखा कि इस टूल की मदद से, परफ़ॉर्मेंस डेटा को कैसे इकट्ठा किया जाता है, देखा जाता है, और उसका विश्लेषण किया जाता है. अब अपनी नई स्किल को, Google Cloud Platform पर चल रही सेवाओं पर लागू किया जा सकता है.
आपने Stackdriver Profiler को कॉन्फ़िगर और इस्तेमाल करने का तरीका जान लिया है!
ज़्यादा जानें
- Stackdriver Profiler: https://cloud.google.com/profiler/
- Go runtime/pprof पैकेज, जिसका इस्तेमाल Stackdriver Profiler करता है: https://golang.org/pkg/runtime/pprof/
लाइसेंस
इस काम के लिए, Creative Commons एट्रिब्यूशन 2.0 जेनेरिक लाइसेंस के तहत लाइसेंस मिला है.