Stackdriver Profiler ile üretim performansını analiz etme

İstemci uygulaması ve ön uç web geliştiricileri, kodlarının performansını artırmak için genellikle Android Studio CPU Profiler veya Chrome'da bulunan profil oluşturma araçları gibi araçları kullanır. Ancak arka uç hizmetleri üzerinde çalışanlar, benzer tekniklere neredeyse hiç erişememiş veya bu teknikleri benimsememiştir. Stackdriver Profiler, kodlarının Google Cloud Platform'da veya başka bir yerde çalışıp çalışmadığına bakılmaksızın, hizmet geliştiricilere bu özelliklerin aynısını sunar.

Bu araç, üretim uygulamalarınızdan CPU kullanımı ve bellek ayırma bilgilerini toplar. Bu bilgileri uygulamanın kaynak koduyla ilişkilendirerek en çok kaynağı tüketen uygulama bölümlerini belirlemenize yardımcı olur ve kodun performans özelliklerini aydınlatır. Araç tarafından kullanılan toplama tekniklerinin düşük ek yükü, aracı üretim ortamlarında sürekli kullanıma uygun hale getirir.

Bu codelab'de, bir Go programı için Stackdriver Profiler'ı nasıl ayarlayacağınızı ve aracın uygulama performansı hakkında ne tür analizler sunabileceğini öğreneceksiniz.

Neler öğreneceksiniz?

  • Stackdriver Profiler ile profil oluşturma için Go programını yapılandırma
  • Stackdriver Profiler ile performans verileri nasıl toplanır, görüntülenir ve analiz edilir?

Gerekenler

  • Google Cloud Platform projesi
  • Chrome veya Firefox gibi bir tarayıcı
  • Vim, EMACs veya Nano gibi standart Linux metin düzenleyicileri hakkında bilgi sahibi olmanız gerekir.

Bu eğitimi nasıl kullanacaksınız?

Yalnızca okuyun Okuyun ve alıştırmaları tamamlayın

Google Cloud Platform deneyiminizi nasıl değerlendirirsiniz?

Başlangıç Orta İleri

Kendi hızınızda ortam kurulumu

Henüz bir Google Hesabınız (Gmail veya Google Apps) yoksa oluşturmanız gerekir. Google Cloud Platform Console'da (console.cloud.google.com) oturum açın ve yeni bir proje oluşturun:

Screenshot from 2016-02-10 12:45:26.png

Proje kimliğini unutmayın. Bu kimlik, tüm Google Cloud projelerinde benzersiz bir addır (Yukarıdaki ad zaten alınmış olduğundan sizin için çalışmayacaktır). Bu codelab'in ilerleyen kısımlarında PROJECT_ID olarak adlandırılacaktır.

Ardından, Google Cloud kaynaklarını kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir.

Bu codelab'i tamamlamak size birkaç dolardan fazla maliyet getirmemelidir. Ancak daha fazla kaynak kullanmaya karar verirseniz veya kaynakları çalışır durumda bırakırsanız maliyet daha yüksek olabilir (bu belgenin sonundaki "temizleme" bölümüne bakın).

Google Cloud Platform'un yeni kullanıcıları 300 ABD doları değerindeki ücretsiz deneme sürümünden yararlanabilir.

Google Cloud Shell

Google Cloud, dizüstü bilgisayarınızdan uzaktan çalıştırılabilir ancak bu codelab'de kurulumu basitleştirmek için bulutta çalışan bir komut satırı ortamı olan Google Cloud Shell'i kullanacağız.

Google Cloud Shell'i etkinleştir

GCP Console'da sağ üstteki araç çubuğunda Cloud Shell simgesini tıklayın:

Ardından "Cloud Shell'i başlat"ı tıklayın:

Ortamın sağlanması ve bağlantının kurulması yalnızca birkaç saniye sürer:

Bu sanal makine, ihtiyaç duyacağınız tüm geliştirme araçlarını içerir. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışır. Bu sayede ağ performansı ve kimlik doğrulama önemli ölçüde iyileştirilir. Bu laboratuvardaki çalışmalarınızın neredeyse tamamını yalnızca bir tarayıcı veya Google Chromebook'unuzla yapabilirsiniz.

Cloud Shell'e bağlandıktan sonra kimliğinizin zaten doğrulandığını ve projenin PROJECT_ID'nize göre ayarlandığını görürsünüz.

Kimliğinizin doğrulandığını onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın:

gcloud auth list

Komut çıkışı

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Komut çıkışı

[core]
project = <PROJECT_ID>

Değilse aşağıdaki komutla ayarlayabilirsiniz:

gcloud config set project <PROJECT_ID>

Komut çıkışı

Updated property [core/project].

Cloud Console'da, sol gezinme çubuğunda "Profiler"ı tıklayarak Profil Oluşturucu kullanıcı arayüzüne gidin:

Alternatif olarak, Profiler kullanıcı arayüzüne gitmek için Cloud Console arama çubuğunu da kullanabilirsiniz. "Stackdriver Profiler" yazıp bulunan öğeyi seçmeniz yeterlidir. Her iki durumda da aşağıdaki gibi "Görüntülenecek veri yok" mesajını içeren Profiler kullanıcı arayüzünü görürsünüz. Proje yeni olduğundan henüz profil oluşturma verisi toplanmamıştır.

Şimdi profil oluşturma zamanı.

Github'da bulunan basit bir sentetik Go uygulaması kullanacağız. Hâlâ açık olan Cloud Shell terminalinde (ve Profiler kullanıcı arayüzünde "Gösterilecek veri yok" mesajı gösterilirken) aşağıdaki komutu çalıştırın:

$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...

Ardından uygulama dizinine geçin:

$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp

Dizin, profilleme aracının etkinleştirildiği sentetik bir uygulama olan "main.go" dosyasını içerir:

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)
        }
        ...
}

Profil oluşturma aracısı varsayılan olarak CPU, yığın ve iş parçacığı profillerini toplar. Buradaki kod, mutex (aynı zamanda "çekişme" olarak da bilinir) profillerinin toplanmasını sağlar.

Şimdi programı çalıştırın:

$ go run main.go

Program çalışırken profilleme aracısı, yapılandırılan beş türün profillerini düzenli olarak toplar. Toplama işlemi zaman içinde rastgele yapılır (her tür için dakikada ortalama bir profil olacak şekilde). Bu nedenle, her türün toplanması üç dakikaya kadar sürebilir. Program, profil oluşturduğunda sizi bilgilendirir. Mesajlar, yukarıdaki yapılandırmada DebugLogging işaretiyle etkinleştirilir. Aksi takdirde, aracı sessizce çalışır:

$ 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
...

Kullanıcı arayüzü, profillerden ilki toplandıktan kısa süre sonra kendini günceller. Bu işlemden sonra otomatik olarak güncellenmez. Yeni verileri görmek için Profil Oluşturucu kullanıcı arayüzünü manuel olarak yenilemeniz gerekir. Bunu yapmak için zaman aralığı seçicideki Şimdi düğmesini iki kez tıklayın:

Kullanıcı arayüzü yenilendikten sonra aşağıdakine benzer bir ekran görürsünüz:

Profil türü seçicide, kullanılabilen beş profil türü gösterilir:

Şimdi her bir profil türünü ve bazı önemli kullanıcı arayüzü özelliklerini inceleyelim, ardından bazı denemeler yapalım. Bu aşamada Cloud Shell terminaline artık ihtiyacınız yoktur. Bu nedenle, CTRL-C tuşlarına basıp "exit" yazarak terminalden çıkabilirsiniz.

Artık bazı veriler topladığımıza göre, bunları daha yakından inceleyelim. Üretimde farklı performans sorunlarının tipik davranışlarını simüle eden sentetik bir uygulama kullanıyoruz (kaynak GitHub'da mevcuttur).

CPU yoğun kod (CPU-intensive Code)

CPU profili türünü seçin. Kullanıcı arayüzü yükledikten sonra, alev grafiğinde load işlevi için dört yaprak bloğu görürsünüz. Bu bloklar, CPU tüketiminin tamamını oluşturur:

Bu işlev, sıkı bir döngü çalıştırarak çok sayıda CPU döngüsü kullanmak için özel olarak yazılmıştır:

main.go

func load() {
        for i := 0; i < (1 << 20); i++ {
        }
}

İşlev, busyloop() işlevinden dört çağrı yolu üzerinden dolaylı olarak çağrılır: busyloop → {foo1, foo2} → {bar, baz} → load. Bir işlev kutusunun genişliği, belirli çağrı yolunun göreceli maliyetini gösterir. Bu durumda dört yolun maliyeti de yaklaşık olarak aynıdır. Gerçek bir programda, performans açısından en önemli olan görüşme yollarını optimize etmeye odaklanmak istersiniz. Daha büyük kutularla daha maliyetli yolları görsel olarak vurgulayan alev grafiği, bu yolların kolayca tanımlanmasını sağlar.

Görüntülemeyi daha da hassaslaştırmak için profil verileri filtresini kullanabilirsiniz. Örneğin, filtre dizesi olarak "baz"ı belirten bir "Yığınları göster" filtresi eklemeyi deneyin. Aşağıdaki ekran görüntüsüne benzer bir şey görmelisiniz. Burada, load() için dört arama yolundan yalnızca ikisi gösterilir. Bu iki yol, adında "baz" dizesi bulunan bir işlevden geçen tek yollardır. Bu tür filtreleme, daha büyük bir programın bir alt bölümüne odaklanmak istediğinizde (örneğin, yalnızca bir bölümünün sahibi olduğunuz için) kullanışlıdır.

Bellek Yoğun Kod (Memory-intensive Code)

Şimdi "Heap" profil türüne geçin. Önceki denemelerde oluşturduğunuz tüm filtreleri kaldırdığınızdan emin olun. Artık allocImpl tarafından çağrılan alloc'nin, uygulamadaki ana bellek tüketicisi olarak gösterildiği bir alev grafiği görmelisiniz:

Alev grafiğinin üzerindeki özet tablo, uygulamada kullanılan toplam bellek miktarının ortalama ~57,4 MiB olduğunu ve bunun büyük bir kısmının allocImpl işlevi tarafından ayrıldığını gösteriyor. Bu işlevin uygulanması göz önüne alındığında bu durum şaşırtıcı değildir:

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))
        }
}

İşlev bir kez yürütülür, daha küçük parçalar halinde 64 MiB ayırır ve ardından bu parçalara yönelik işaretçileri çöp toplama işleminden korunmaları için genel bir değişkende depolar. Profiler tarafından kullanılan bellek miktarının 64 MiB'den biraz farklı olduğunu unutmayın: Go yığın profiler, istatistiksel bir araçtır. Bu nedenle ölçümler düşük ek yüke sahiptir ancak bayt açısından doğru değildir. Bu gibi durumlarda% 10'luk bir fark görmeniz sizi şaşırtmamalıdır.

IO yoğun kod (IO-intensive Code)

Profil türü seçicide "Threads"i seçerseniz görünüm, genişliğin büyük bir kısmının wait ve waitImpl işlevleri tarafından kullanıldığı bir alev grafiğine dönüşür:

Alev grafiğinin üzerindeki özette, wait işlevinden çağrı yığınlarını büyüten 100 goroutine olduğunu görebilirsiniz. Bu beklemeleri başlatan kodun şu şekilde göründüğü göz önüne alındığında bu ifade tamamen doğrudur:

main.go

func main() {
        ...
        // Simulate some waiting goroutines.
        for i := 0; i < 100; i++ {
                go wait()
        }

Bu profil türü, programın beklenmedik bir süre boyunca beklemede (ör. G/Ç) kalıp kalmadığını anlamak için faydalıdır. Bu tür çağrı yığınları, CPU süresinin önemli bir bölümünü tüketmedikleri için genellikle CPU profiler'ı tarafından örneklenmez. Genellikle ileti dizisi profilleriyle "Yığınları gizle" filtrelerini kullanmak isteyebilirsiniz. Örneğin, gopark, ile biten tüm yığınları gizlemek için bu filtreleri kullanabilirsiniz. Çünkü bu yığınlar genellikle boşta olan gorutinlerdir ve G/Ç üzerinde bekleyenlerden daha az ilgi çekicidir.

İş parçacıkları profil türü, programda iş parçacıklarının programın başka bir bölümüne ait bir mutex'i uzun süre beklediği noktaları belirlemeye de yardımcı olabilir ancak bu amaç için aşağıdaki profil türü daha kullanışlıdır.

Çakışma Yoğun Kod (Contention-intensive Code)

Çakışma profili türü, programdaki en çok "istenen" kilitleri tanımlar. Bu profil türü Go programlarında kullanılabilir ancak aracı yapılandırma kodunda "MutexProfiling: true" belirtilerek açıkça etkinleştirilmelidir. Toplama işlemi, belirli bir kilit bir gorutin A tarafından açılırken başka bir gorutin B'nin kilidin açılmasını beklediği zamanların sayısını ("Çekişmeler" metriği altında) kaydederek çalışır. Ayrıca, engellenen goroutine'in kilidi beklediği süreyi de ("Gecikme" metriği altında) kaydeder. Bu örnekte tek bir çekişme yığını vardır ve kilidin toplam bekleme süresi 11, 03 saniyedir:

Bu profili oluşturan kod, bir mutex üzerinde kavga eden 4 goroutineden oluşur:

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)
        }
}

Bu laboratuvarda, bir Go programının Stackdriver Profiler ile kullanılmak üzere nasıl yapılandırılacağını öğrendiniz. Ayrıca bu araçla performans verilerini nasıl toplayacağınızı, görüntüleyeceğinizi ve analiz edeceğinizi de öğrendiniz. Artık yeni becerinizi Google Cloud Platform'da çalıştırdığınız gerçek hizmetlere uygulayabilirsiniz.

Stackdriver Profiler'ı nasıl yapılandıracağınızı ve kullanacağınızı öğrendiniz.

Daha Fazla Bilgi

Lisans

Bu çalışma, Creative Commons Attribution 2.0 Genel Amaçlı Lisans ile lisans altına alınmıştır.