در حالی که برنامهنویسهای برنامههای مشتری و توسعهدهندگان وب ظاهری معمولاً از ابزارهایی مانند نمایهساز CPU Android Studio یا ابزارهای نمایهسازی موجود در Chrome برای بهبود عملکرد کد خود استفاده میکنند، تکنیکهای معادل تقریباً توسط افرادی که روی خدمات پشتیبان کار میکنند در دسترس یا به خوبی مورد استفاده قرار نگرفتهاند. Stackdriver Profiler این قابلیتها را برای توسعهدهندگان خدمات ارائه میکند، صرف نظر از اینکه کد آنها در پلتفرم Google Cloud یا جاهای دیگر اجرا میشود.

این ابزار اطلاعات استفاده از CPU و تخصیص حافظه را از برنامه های تولیدی شما جمع آوری می کند. این اطلاعات را به کد منبع برنامه نسبت می دهد و به شما کمک می کند تا قسمت هایی از برنامه را که بیشترین منابع را مصرف می کنند شناسایی کنید و در غیر این صورت ویژگی های عملکرد کد را روشن می کند. سربار کم تکنیک های مجموعه به کار رفته توسط این ابزار، آن را برای استفاده مداوم در محیط های تولیدی مناسب می کند.
در این کد لبه، نحوه راهاندازی Stackdriver Profiler را برای برنامه Go یاد خواهید گرفت و با چه نوع بینشهایی در مورد عملکرد برنامه کاربردی ابزار میتواند ارائه دهد، آشنا میشوید.
چیزی که یاد خواهید گرفت
- نحوه پیکربندی یک برنامه Go برای پروفایل با Stackdriver Profiler.
- نحوه جمع آوری، مشاهده و تجزیه و تحلیل داده های عملکرد با Stackdriver Profiler.
آنچه شما نیاز دارید
- یک پروژه Google Cloud Platform
- یک مرورگر، مانند کروم یا فایرفاکس
- آشنایی با ویرایشگرهای متن استاندارد لینوکس مانند Vim، EMACs یا Nano
چگونه از این آموزش استفاده خواهید کرد؟
تجربه خود را با Google Cloud Platform چگونه ارزیابی می کنید؟
تنظیم محیط خود به خود
اگر قبلاً یک حساب Google (Gmail یا Google Apps) ندارید، باید یک حساب ایجاد کنید . به کنسول Google Cloud Platform ( consol.cloud.google.com ) وارد شوید و یک پروژه جدید ایجاد کنید:
شناسه پروژه را به خاطر بسپارید، یک نام منحصر به فرد در تمام پروژه های Google Cloud (نام بالا قبلاً گرفته شده است و برای شما کار نخواهد کرد، متأسفیم!). بعداً در این آزمایشگاه کد به عنوان PROJECT_ID نامیده خواهد شد.
در مرحله بعد، برای استفاده از منابع Google Cloud، باید صورتحساب را در کنسول Cloud فعال کنید .
گذراندن این کد نباید بیش از چند دلار هزینه داشته باشد، اما اگر تصمیم به استفاده از منابع بیشتری داشته باشید یا آنها را در حال اجرا رها کنید، ممکن است بیشتر باشد (به بخش "پاکسازی" در انتهای این سند مراجعه کنید).
کاربران جدید Google Cloud Platform واجد شرایط استفاده آزمایشی رایگان 300 دلاری هستند.
Google Cloud Shell
در حالی که Google Cloud را میتوان از راه دور از لپتاپ شما کار کرد، برای سادهتر کردن راهاندازی در این کد، از Google Cloud Shell استفاده میکنیم، یک محیط خط فرمان که در Cloud اجرا میشود.
Google Cloud Shell را فعال کنید
از کنسول GCP روی نماد Cloud Shell در نوار ابزار بالا سمت راست کلیک کنید:
سپس روی "Start 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 را با پیام «عدم نمایش دادهای» مانند زیر ببینید. این پروژه جدید است، بنابراین هنوز هیچ داده پروفایلی جمع آوری نشده است.

اکنون زمان آن است که چیزی را نمایه کنید!
ما از یک برنامه ساده Go مصنوعی موجود در Github استفاده خواهیم کرد. در ترمینال 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)
}
...
}عامل پروفایل به طور پیش فرض پروفایل های CPU، Heap و Thread را جمع آوری می کند. کد در اینجا مجموعه پروفایل های mutex (همچنین به عنوان "contention" شناخته می شود) را فعال می کند.
حالا برنامه را اجرا کنید:
$ 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 ...
پس از جمعآوری اولین نمایهها، UI خود را بهروزرسانی میکند. پس از آن بهروزرسانی خودکار نمیشود، بنابراین برای دیدن دادههای جدید، باید رابط کاربری Profiler را بهصورت دستی بازخوانی کنید. برای انجام این کار، روی دکمه Now در انتخابگر بازه زمانی دو بار کلیک کنید:

پس از رفرش کردن رابط کاربری، چیزی شبیه به این خواهید دید:

انتخابگر نوع نمایه پنج نوع نمایه موجود را نشان می دهد:

بیایید اکنون هر یک از انواع پروفایل ها و برخی از قابلیت های مهم رابط کاربری را مرور کنیم و سپس آزمایش هایی را انجام دهیم. در این مرحله دیگر نیازی به ترمینال Cloud Shell ندارید، بنابراین می توانید با فشردن CTRL-C و تایپ «exit» از آن خارج شوید.
اکنون که برخی داده ها را جمع آوری کرده ایم، بیایید با دقت بیشتری به آن نگاه کنیم. ما از یک برنامه مصنوعی استفاده می کنیم (منبع در Github موجود است) که رفتارهای معمول انواع مختلف مشکلات عملکرد در تولید را شبیه سازی می کند.
کد فشرده CPU
نوع پروفایل CPU را انتخاب کنید. پس از اینکه رابط کاربری آن را بارگیری کرد، در نمودار شعله بلوک های چهار برگ برای عملکرد load را مشاهده خواهید کرد که مجموعاً تمام مصرف CPU را شامل می شود:

این تابع به طور خاص برای مصرف چرخه های CPU زیادی با اجرای یک حلقه تنگ نوشته شده است:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}تابع به طور غیر مستقیم از busyloop () از طریق چهار مسیر فراخوانی فراخوانی می شود: busyloop → { foo1 , foo2 } → { bar , baz } → load . عرض یک جعبه تابع نشان دهنده هزینه نسبی مسیر تماس خاص است. در این حالت هر چهار مسیر تقریباً هزینه یکسانی دارند. در یک برنامه واقعی، شما می خواهید روی بهینه سازی مسیرهای تماس که بیشترین اهمیت را از نظر عملکرد دارند، تمرکز کنید. نمودار شعله، که به صورت بصری بر مسیرهای گرانتر با جعبههای بزرگتر تأکید میکند، شناسایی این مسیرها را آسان میکند.
برای اصلاح بیشتر نمایشگر می توانید از فیلتر داده پروفایل استفاده کنید. به عنوان مثال، سعی کنید یک فیلتر "Show stacks" اضافه کنید و "baz" را به عنوان رشته فیلتر مشخص کنید. شما باید چیزی شبیه به اسکرین شات زیر ببینید، که در آن تنها دو مسیر از چهار مسیر تماس برای load() نمایش داده می شود. این دو مسیر تنها مسیرهایی هستند که از تابعی با رشته "baz" در نام آن عبور می کنند. چنین فیلترینگی زمانی مفید است که میخواهید روی بخش فرعی یک برنامه بزرگتر تمرکز کنید (مثلاً، زیرا شما فقط مالک بخشی از آن هستید).

کد حافظه فشرده
اکنون به نوع پروفایل "Heap" بروید. مطمئن شوید که فیلترهایی را که در آزمایشهای قبلی ایجاد کردهاید حذف کردهاید. اکنون باید نمودار شعله ای را مشاهده کنید که در آن allocImpl که توسط alloc فراخوانی می شود، به عنوان مصرف کننده اصلی حافظه در برنامه نمایش داده می شود:

جدول خلاصه بالای نمودار شعله نشان می دهد که مقدار کل حافظه استفاده شده در برنامه به طور متوسط ~ 57.4 مگابایت است که بیشتر آن توسط تابع 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 مگابایت را در تکههای کوچکتر تخصیص میدهد، سپس نشانگرها را به آن تکهها در یک متغیر سراسری ذخیره میکند تا از جمعآوری زباله محافظت کند. توجه داشته باشید که مقدار حافظه نشاندادهشده توسط پروفیلکننده کمی متفاوت از 64 مگابایت است: نمایهساز هیپ Go یک ابزار آماری است، بنابراین اندازهگیریها سربار پایین هستند اما بایت دقیق نیستند. با دیدن تفاوت 10 درصدی این چنینی تعجب نکنید.
کد IO-intensive
اگر "Threads" را در انتخابگر نوع نمایه انتخاب کنید، نمایشگر به نمودار شعله ای تغییر می کند که در آن بیشتر عرض با توابع wait و waitImpl گرفته می شود:

در خلاصه نمودار شعله بالا، می توانید ببینید که 100 گوروتین وجود دارد که پشته تماس خود را از تابع wait رشد می دهند. این دقیقاً درست است، با توجه به اینکه کدی که این انتظارها را شروع می کند به این صورت است:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}این نوع نمایه برای درک اینکه آیا برنامه زمان غیرمنتظره ای را در انتظار صرف می کند (مانند I/O) مفید است. چنین پشته های فراخوانی معمولاً توسط نمایه ساز CPU نمونه برداری نمی شوند، زیرا آنها بخش قابل توجهی از زمان CPU را مصرف نمی کنند. اغلب میخواهید از فیلترهای «پنهان کردن پشتهها» با نمایههای Threads استفاده کنید - برای مثال، برای مخفی کردن همه پشتههایی که با تماس به gopark, زیرا آنها اغلب گوروتینهای بیکار هستند و جالبتر از مواردی هستند که در I/O منتظر میمانند.
نوع پروفایل رشته ها همچنین می تواند به شناسایی نقاطی در برنامه کمک کند که در آن نخ ها برای مدت طولانی منتظر mutex متعلق به قسمت دیگری از برنامه هستند، اما نوع پروفایل زیر برای آن مفیدتر است.
کد مناقشه برانگیز
نوع نمایه Contention بیشترین قفل های "تحت تعقیب" را در برنامه شناسایی می کند. این نوع نمایه برای برنامههای Go موجود است، اما باید به صراحت با مشخص کردن " MutexProfiling: true " در کد پیکربندی عامل فعال شود. این مجموعه با ثبت (تحت معیار "مشاهده") تعداد دفعاتی که یک قفل خاص، هنگام باز شدن توسط یک گوروتین A، یک گوروتین B دیگر در انتظار باز شدن قفل بود، کار می کند. همچنین (تحت معیار "تاخیر") زمانی را که گوروتین مسدود شده برای قفل منتظر ماند ثبت می کند. در این مثال، یک پشته منازعه وجود دارد و کل زمان انتظار برای قفل 11.03 ثانیه بود:

کدی که این نمایه را ایجاد می کند شامل 4 گوروتین است که بر سر یک mutex می جنگند:
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)
}
}در این آزمایشگاه، یاد گرفتید که چگونه یک برنامه Go را می توان برای استفاده با Stackdriver Profiler پیکربندی کرد. همچنین نحوه جمع آوری، مشاهده و تجزیه و تحلیل داده های عملکرد را با این ابزار یاد گرفتید. اکنون میتوانید مهارت جدید خود را در سرویسهای واقعی که در Google Cloud Platform اجرا میکنید، اعمال کنید.
شما یاد گرفتید که چگونه Stackdriver Profiler را پیکربندی و استفاده کنید!
بیشتر بدانید
- Stackdriver Profiler: https://cloud.google.com/profiler/
- بسته اجرا/pprof را که Stackdriver Profiler استفاده میکند: https://golang.org/pkg/runtime/pprof/
مجوز
این اثر تحت مجوز Creative Commons Attribution 2.0 Generic مجوز دارد.