Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 12326] ファイルの概要

このコミットは、Go言語の標準ライブラリであるosパッケージにおいて、Windows環境でのプロセスのユーザー時間(UserTime)とシステム時間(SystemTime)の取得を正確に実装するための変更です。具体的には、Windows APIのGetProcessTimes関数をGoのsyscallパッケージを通じて呼び出し、その結果をos.ProcessStateに反映させることで、これまで常に0を返していたこれらのメトリックが正しく報告されるようになります。

変更されたファイルは以下の通りです。

  • src/pkg/os/exec_windows.go: os.Processが終了した際に、プロセスの時間情報を取得し、ProcessStateに格納するロジックが追加されました。また、ProcessStateuserTime()systemTime()メソッドが、取得した時間情報に基づいて正しい値を返すように修正されました。
  • src/pkg/syscall/syscall_windows.go: Windows APIのGetProcessTimes関数のGo言語でのシグネチャが追加され、syscall.Rusage構造体がプロセスの時間情報を保持できるように拡張されました。
  • src/pkg/syscall/zsyscall_windows_386.go: 32ビット(x86)アーキテクチャ向けのGetProcessTimesシステムコールラッパーの実装が追加されました。
  • src/pkg/syscall/zsyscall_windows_amd64.go: 64ビット(x64)アーキテクチャ向けのGetProcessTimesシステムコールラッパーの実装が追加されました。
  • src/pkg/syscall/ztypes_windows.go: Filetime構造体のNanoseconds()メソッドに関するコメントが追加されました。

コミット

commit c3fbc9a5e817d037f04abba4ec6d8a453afb344a
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Fri Mar 2 14:47:40 2012 +1100

    os: implement UserTime/SystemTime on windows
    
    Fixes #3145.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5721044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c3fbc9a5e817d037f04abba4ec6d8a453afb344a

元コミット内容

os: implement UserTime/SystemTime on windows

Fixes #3145.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5721044

変更の背景

Go言語のosパッケージは、実行中のプロセスに関する情報を提供する機能を持っています。特に、os.ProcessState構造体は、プロセスが終了した後の状態(終了コード、CPU時間など)をカプセル化します。しかし、このコミットがなされる以前のWindows環境では、ProcessStateが提供するUserTime()(ユーザーモードでのCPU時間)とSystemTime()(カーネルモードでのCPU時間)のメソッドが常に0を返していました。これは、Windowsの基盤となるAPIからこれらの時間情報を適切に取得し、Goのデータ構造にマッピングする実装が欠けていたためです。

この問題は、GoのIssueトラッカーでIssue #3145として報告されていました。プロセスのCPU使用時間を正確に測定することは、パフォーマンス分析、リソース管理、およびシステム監視において非常に重要です。例えば、アプリケーションのボトルネックを特定したり、特定のプロセスがシステムリソースをどれだけ消費しているかを把握したりするために、これらのメトリックは不可欠です。このコミットは、Windows上でのGoアプリケーションのプロファイリングと監視能力を向上させることを目的としています。

前提知識の解説

このコミットの変更内容を理解するためには、以下の概念について知っておく必要があります。

  1. Go言語のosパッケージ:

    • Goの標準ライブラリの一部で、オペレーティングシステムとのインタラクションを提供します。ファイルシステム操作、プロセス管理、環境変数へのアクセスなどが含まれます。
    • os.Process: 実行中のプロセスを表す構造体です。
    • os.ProcessState: os.Processが終了した後に、そのプロセスの最終状態(終了コード、CPU時間など)を保持する構造体です。UserTime()SystemTime()メソッドを持ち、それぞれユーザーモードとシステムモードでのCPU時間を返します。
  2. Go言語のsyscallパッケージ:

    • オペレーティングシステムが提供する低レベルなシステムコールへのインターフェースを提供します。これにより、GoプログラムからOS固有の機能(例えば、Windows APIやLinuxのprctlなど)を直接呼び出すことができます。
    • このパッケージは、OSに依存する機能の実装によく使用されます。
  3. Windows API GetProcessTimes関数:

    • Windowsオペレーティングシステムが提供するAPI関数の一つで、指定されたプロセスのタイミング情報を取得するために使用されます。
    • この関数は、以下の4つのFILETIME構造体へのポインタを引数として取ります。
      • lpCreationTime: プロセスが作成された時刻。
      • lpExitTime: プロセスが終了した時刻。
      • lpKernelTime: プロセスがカーネルモードで実行された時間。
      • lpUserTime: プロセスがユーザーモードで実行された時間。
    • これらの時間は、プロセスがCPUを消費した合計時間を示します。
  4. FILETIME構造体 (Windows):

    • Windows APIで使用される時間表現の構造体です。
    • 1601年1月1日(UTC)からの100ナノ秒間隔の数を64ビット整数で表します。HighDateTime(上位32ビット)とLowDateTime(下位32ビット)の2つのDWORD(32ビット符号なし整数)フィールドで構成されます。
  5. Rusage構造体 (Resource Usage):

    • 元々はUnix系のシステムでプロセスのリソース使用状況(CPU時間、メモリ使用量など)を報告するために使用される構造体です。
    • Goのsyscallパッケージでは、各OSの特性に合わせてこの概念を実装しています。このコミットでは、Windows固有のプロセス時間情報を保持するために拡張されました。
  6. ユーザー時間(User Time)とシステム時間(System Time):

    • ユーザー時間: プロセスがユーザーモードでCPU命令を実行するのに費やした時間です。アプリケーションコードの実行、ライブラリ関数の呼び出しなどがこれに該当します。
    • システム時間(カーネル時間): プロセスがカーネルモードでCPU命令を実行するのに費やした時間です。システムコール(ファイルI/O、ネットワーク通信、メモリ割り当てなど)の実行、OSカーネルの処理などがこれに該当します。
    • これらの時間は、プロセスの総CPU使用時間を構成します。
  7. Go言語のtime.Duration:

    • Goの標準ライブラリtimeパッケージで定義されている型で、時間の長さを表します。ナノ秒単位の整数として内部的に表現されます。

技術的詳細

このコミットの技術的な核心は、Windows固有のプロセス時間取得APIであるGetProcessTimesをGoのsyscallパッケージを通じて利用し、その結果をosパッケージのProcessStateに適切にマッピングすることにあります。

  1. syscall.GetProcessTimesの導入:

    • src/pkg/syscall/syscall_windows.goに、Windows APIのGetProcessTimesに対応するGoの関数シグネチャが追加されました。これにより、GoプログラムからこのAPIを型安全に呼び出すことが可能になります。
    • 実際のシステムコールへのディスパッチは、アーキテクチャ固有のファイル(zsyscall_windows_386.gozsyscall_windows_amd64.go)で行われます。これらのファイルでは、Syscall6関数(6つの引数を取るシステムコールを呼び出すためのGoの内部関数)を使用して、GetProcessTimesのDLLエントリポイントを呼び出しています。
  2. syscall.Rusage構造体の拡張:

    • src/pkg/syscall/syscall_windows.goにおいて、Rusage構造体がWindowsのGetProcessTimesが返す4つのFILETIMEフィールド(CreationTime, ExitTime, KernelTime, UserTime)を持つように変更されました。これにより、プロセス時間情報をGoの構造体として保持できるようになります。
  3. os.Process.wait()での時間情報取得:

    • src/pkg/os/exec_windows.goProcess.wait()メソッド(プロセスが終了するのを待ち、その状態を返すメソッド)内で、syscall.GetProcessTimesが呼び出されるようになりました。
    • プロセスのハンドルと、syscall.Rusage構造体のアドレスを引数として渡し、GetProcessTimesから返される時間情報をRusageインスタンスに格納します。
    • このRusageインスタンスは、ProcessState構造体の新しいフィールドとして渡されるようになります。
  4. ftToDurationヘルパー関数の導入:

    • src/pkg/os/exec_windows.goftToDurationという新しいヘルパー関数が追加されました。
    • この関数は、syscall.Filetime構造体へのポインタを受け取り、それをGoのtime.Duration型に変換します。
    • FILETIMEは100ナノ秒単位で時間を表現するため、ftToDurationFILETIMEの値を100倍し、さらにtime.Nanosecondを掛けることで、正確なtime.Duration値に変換します。
  5. os.ProcessState.userTime()os.ProcessState.systemTime()の実装:

    • src/pkg/os/exec_windows.goにおいて、これまで常に0を返していたProcessState.userTime()ProcessState.systemTime()メソッドが、ProcessStateに格納されたRusage構造体のUserTimeKernelTimeフィールドをftToDurationで変換した値を返すように修正されました。

これらの変更により、GoプログラムはWindows上で実行されるプロセスの正確なユーザー時間とシステム時間を取得できるようになり、Goのクロスプラットフォームなプロセス管理機能がWindowsにおいてもより完全なものとなりました。

コアとなるコードの変更箇所

src/pkg/os/exec_windows.go

 // BUG(rsc): On Windows, ProcessState's UserTime and SystemTime methods always return 0.
+func ftToDuration(ft *syscall.Filetime) time.Duration {
+	n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals
+	return time.Duration(n*100) * time.Nanosecond
+}
 
 func (p *ProcessState) userTime() time.Duration {
-	return 0
+	return ftToDuration(&p.rusage.UserTime)
 }
 
 func (p *ProcessState) systemTime() time.Duration {
-	return 0
+	return ftToDuration(&p.rusage.KernelTime)
 }
 func (p *Process) wait() (ps *ProcessState, err error) {
 	// ... (既存のコード) ...
 	if e != nil {
 		return nil, NewSyscallError("GetExitCodeProcess", e)
 	}
+	var u syscall.Rusage
+	e = syscall.GetProcessTimes(syscall.Handle(p.handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime)
+	if e != nil {
+		return nil, NewSyscallError("GetProcessTimes", e)
+	}
 	p.done = true
 	defer p.Release()
-	return &ProcessState{p.Pid, syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil
+	return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
 }

src/pkg/syscall/syscall_windows.go

 //sys	GetExitCodeProcess(handle Handle, exitcode *uint32) (err error)\n
 //sys	GetStartupInfo(startupInfo *StartupInfo) (err error) = GetStartupInfoW\n
 //sys	GetCurrentProcess() (pseudoHandle Handle, err error)\n
+//sys	GetProcessTimes(handle Handle, creationTime *Filetime, exitTime *Filetime, kernelTime *Filetime, userTime *Filetime) (err error)\n
 //sys	DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error)\n
 //sys	WaitForSingleObject(handle Handle, waitMilliseconds uint32) (event uint32, err error) [failretval==0xffffffff]\n
 //sys	GetTempPath(buflen uint32, buf *uint16) (n uint32, err error) = GetTempPathW\n
 // Invented structures to support what package os expects.\n
-type Rusage struct{}\n
+type Rusage struct {\n
+\tCreationTime Filetime\n+\tExitTime     Filetime\n+\tKernelTime   Filetime\n+\tUserTime     Filetime\n+}\n

コアとなるコードの解説

src/pkg/os/exec_windows.goの変更点

  1. ftToDuration関数の追加:

    • この関数は、Windows APIのFILETIME構造体(syscall.FiletimeとしてGoにマッピングされる)をGoのtime.Duration型に変換するためのユーティリティです。
    • FILETIMEは100ナノ秒単位の64ビット整数で時間を表現するため、ft.HighDateTimeを32ビット左シフトしてft.LowDateTimeと結合することで、完全な64ビットのナノ秒間隔数を取得します。
    • その結果に100を掛け、さらにtime.Nanosecondを掛けることで、正確なtime.Duration値(ナノ秒単位)に変換しています。
  2. ProcessState.userTime()ProcessState.systemTime()の修正:

    • これらのメソッドは、ProcessState構造体内のrusageフィールド(syscall.Rusage型)から、それぞれUserTimeKernelTime(Windowsではシステム時間に対応)のFiletime値を取り出します。
    • 取り出したFiletime値を新しく追加されたftToDuration関数に渡し、time.Duration型に変換された正確なCPU時間を返します。これにより、Windows環境でもこれらのメソッドが意味のある値を返すようになりました。
  3. Process.wait()メソッドの変更:

    • プロセスが終了し、その終了コードを取得した後、syscall.Rusage型の変数uを宣言します。
    • syscall.GetProcessTimes関数を呼び出し、プロセスのハンドルとuの各時間フィールドへのポインタを渡します。これにより、プロセスの作成時間、終了時間、カーネル時間、ユーザー時間がuに格納されます。
    • GetProcessTimesの呼び出しが失敗した場合は、適切なエラーを返します。
    • 最後に、ProcessStateを構築する際に、これまでの空のsyscall.Rusageインスタンスの代わりに、取得した時間情報が格納された&uを渡すように変更されました。これにより、ProcessStateがプロセスの正確な時間情報を持つことができるようになります。

src/pkg/syscall/syscall_windows.goの変更点

  1. GetProcessTimesシステムコール定義の追加:

    • //sysコメントは、GoのsyscallパッケージがWindows API関数をGoの関数として公開するための特別なディレクティブです。
    • //sys GetProcessTimes(handle Handle, creationTime *Filetime, exitTime *Filetime, kernelTime *Filetime, userTime *Filetime) (err error)という行が追加され、GoプログラムからGetProcessTimesを呼び出す際のシグネチャが定義されました。
  2. Rusage構造体の拡張:

    • syscall.Rusage構造体が、WindowsのGetProcessTimesが返す4つのFiletimeフィールド(CreationTime, ExitTime, KernelTime, UserTime)を含むように定義されました。これにより、Windows固有のプロセス時間情報をGoの型システム内で表現できるようになります。

これらの変更により、GoのosパッケージはWindows環境でプロセスのCPU使用時間を正確に報告できるようになり、Goアプリケーションのパフォーマンス監視やプロファイリングの精度が向上しました。

関連リンク

参考にした情報源リンク