ソフトウェア品質を高める開発者テスト

  • Shift Left:上流テスト(Developer's Test)を増やせば下流のテストはしなくてもいい)
  • multidisciplinary team whose members work together from start to finish
  • アジャイルにおいてはシフトレフトの活動は必須であり、なおシステムテストですべき活動は自組織で定義しなければならない。

 

Ch2

・品質向上システムテストをちゃんとやる

・上流で品質を担保したいマネージメントvs忙しいエンジニア

・プロジェクトの後半でバグをつぶすコストは、前半の故周防の数倍かかる

・プロジェクトの後半でバグがみつかると、出荷後にみつかるリスクが増える。

・上流テストをしない=出荷後のバグが発生する

 

ch3

・クラス図とシーケンス図は書くべき、ビッグクラスを防げる。リファクタリングの見えるかができる。

・テストにはフェーズがある単体テスト→総合テスト→システムテスト

・各フェーズで正しいテストメソッドを組み合わせる必要がある。

・単体テストで重要なメソッドは3つ、境界値テスト、状態遷移テスト、組み合わせテスト

・境界値テストでバグは80%防げる

・境界値テストでは、まず開発者としてどのような間違った実装をする可能性があるか考える。そこからテストケースを作っていく。

・状態遷移テストは、正常の状態遷移、異常系の状態遷移のエラー処理などを正しく明文化して、テスト担当者につたえる必要がある。

バビロン大富豪の教え

books.rakuten.co.jp

この本には目先のお金を増やす方法ではなく、一生を通しての資産形成をする上でのマインドセットと幸せとは何かが書かれている。

 

第1章

7つの道具

その1 「収入の10分の1を貯金せよ」

収入の9割しか使えないお金と捉えれば生活レベルは下げなくても貯金はできる。

1割は最低限で、2割貯金できると老後に余裕ができる。

平均的なサラリーマンの生涯年収は3億円、その2割は6000万になる。年利3%で運用できると仮定すると20年で1億円になる

その2 「欲望に優先順位をつけよ」

本当に欲しい物、必要な物にだけお金を使う。

その3 「蓄えたお金を働かせよ」

これが一番大事。ローリスクの長期投資をするべき。

その4 「危険や天敵から金を堅守せよ」

世の中をお金を使わせようとする誘惑が多い、それらに惑わされない。

その5 「より良きところに住め」

いいところに住むと、そこに住んでいる人の知的レベルや考え方が浸透し自分にいい影響になる。

その6 「今日から未来の生活に備えよ」

すぐに行動する

その7 「自分こそを最大の資本とせよ」

自分に積極的に投資する、

 

お金と幸せを生み出す5つの黄金法則

3. 黄金の扱いに秀でた者の助言に、熱心に耳を傾ける

4. 自分が理解していない商いは避ける

5. 非現実的な利益を出そうとしない

 

第6章

一番大事な資産はお金ではなく、何もしなくてもお金が生み出される仕組み。

 

第7章

仕事の捉え方。人は潜在的に人に何かしてあげたい欲求がある。その欲求をみたすために仕事をするべき、お金はおまけ。

JUnit in Action, Third Edition Chapter 5 Software testing principles, Chapter 6 Test quality

learning.oreilly.com

5 Software testing principles

   5.1.3 Detecting regressions and limiting debugging

passing unit test suite confirms that your code works and gives you the confidence to modify your existing code, either for refactoring or to add and modify new features.

コードに手を加える際の自信は結局は調査工数の削減にもなるのではと思う。

 

   5.1.6 Documenting expected behavior

unit tests match the production code, they must be up to date, unlike other forms of documentation

ドキュメントとしてのテストという発想は自分の中では無かったので新しい発見。

 

6 Test quality

Test coverage can ensure some of the quality of your programming, but it is also a controversial metric. High code coverage does not tell you anything about the quality of the tests. A good programmer should be able to see beyond the pure percentage obtained by running tests.

coverage 以外でテストの品質を定量的に図る方法ないのかな?

 

     6.1.1 Introduction to test coverage

上の図はblack box tests とUnit testsのコードカバレッジを比べた図。画面からのテストだけどは実装の都合上通ることないコードが発生するのでカバレッジ100%は難しい。だからUnit Testが必要。

   

     6.2.1 Understanding that public APIs are contracts

Imagine a public method that takes a distance as a double parameter and uses a black-box test to verify a computation. At some point, the meaning of the parameter changes from miles to kilometers. Your code still compiles, but the runtime breaks. Without a unit test to fail and tell you what is wrong, you may spend a lot of time debugging and talking to angry customers.

この本ではこのような問題の対策としてUnit testを推奨しているが、Domain Driven Developmentの本にはprimitive type でもCustom Classにwrapして引数として渡すべきとある。このケースだと DistanceInKilometer , DistanceInMiles みたいなクラスにdoubleを格納してあげるとよさそう。

 

           6.2.2 Reducing dependencies

To write testable code, you should reduce dependencies as much as possible. If your classes depend on many other classes that need to be instantiated and set up with some state, your tests will be very complicated

この問題に対するSolution

before

class Vehicle {
 
   Driver d = new Driver();
   boolean hasDriver = true;
 
   private void setHasDriver(boolean hasDriver) {
      this.hasDriver = hasDriver;
   }
}

after

class Vehicle {
 
   Driver d;
   boolean hasDriver = true;
 
   Vehicle(Driver d) {
      this.d = d;
   }
 
   private void setHasDriver(boolean hasDriver) {
      this.hasDriver = hasDriver;
   }
}

依存はDIしてあげたほうがMockオブジェクトが作りやすくてテストがしやすい。

 

            6.2.4 Following the Law of Demeter (Principle of Least Knowledge)

The Law of Demeter, or Principle of Least Knowledge, states that one class should know only as much as it needs to know.

メソッドの引数に渡す情報は最小限にしてあげるとテストが書きやすい。

 

Java Performance 2nd Edition, Chapter 5, 6 Garbage Collection

Chapter 5. An Introduction to Garbage Collection

the GC algorithm scans all objects that are reachable via one of the root objects. Objects that are reachable via a GC root are live objects; the remaining unreachable objects are garbage

引用:Garbage Collection in Java - w3resource

GC RootsはHeap内に生成されたオブジェクトに対するRefarenceをもっているオブジェクト。GC RootsがRefarenceを持っていないオブジェクトがGC対象になる。

GC Rootsには主に4種類あって、

  1. local variable
  2. active threads
  3. static variables
  4. JNI references

 

the performance of GC is dominated by these basic operations: finding unused objects, making their memory available, and compacting the heap

図があるとわかりやすい。

 

The pauses when all application threads are stopped are called stop-the-world pauses. These pauses generally have the greatest impact on the performance of an application

GC Threadが走って、Memory内のオブジェクトを移動するとき(上の図でいうCompaction)にGC以外の稼働中のThreadがオブジェクト参照できない時間が発生する。

 

 the phase where they scan for unused objects can occur without stopping application threads, these algorithms are called concurrent collectors.

stop-the-worldを必要としないのは魅力的だが、トレードオフとしてconcurrent collectorsはCPUコストが高いのでシステム全体のパフォーマンスを下げる。

 

GC Algorithms

GC Algorithms Description Pro Con flag
Serial GC

Uses single thread to process the heap

Suitable for one CPU environment

Suitable for Docker containers Stops application for both minor and full GC -XX:+UseSerialGC
Throughput(Parallel) GC

Uses multiple thread to collect the young generation.

Default GC in JDK 8

Minor GC is faster than serial Stops application for both minor and full GC -XX:+UseParallelGC
G1 GC

Concurrent collection.

It collects area with high garabage density first

Default GC from JDK 11

minimum application pause

 

It uses CPU resources -XX:+UseG1GC  
CMS Collector

Concurrent collection

Depricated in JDK 11

Not recommended

 

Memory will be fragmented

It stops application if memory is fragmented

-XX:+UseConcMarkSweepGC
ZGC Experimental GC    

-XX:+UnlockExperimentalVMOptions

-XX:+UseZGC

 
Shenandoah Experimental GC    

-XX:+UnlockExperimentalVMOptions

-XX:+UseShenandoahGC

 
Epsilon GC

No Collection

Suitable for very short-lived programs

   

-XX:+UnlockExperimentalVMOptions

-XX:+UseEpsilonGC

 

 

Sizing the Heap

simply specifying a very large heap isn’t necessarily the answer either. The time spent in GC pauses is dependent on the size of the heap, so as the size of the heap increases, the duration of those pauses also increases.

Heapサイズを増やすだけでもパフォーマンステストは実施した方がよさそう。

 

Useful commands

-Xmx : Specify heap

-XX:NewRatio= N : Set the ration of young generation to the old generation

-XX:-UseAdaptiveSizePolicy : (true by default) dynamnically configuare size of heap, the generations, and the survivor spaces to find optimal performance.

 

Enabling GC Logging in JDK 8

-verbose:gc : Create a simple GC logs

-XX:+PrintGCDetails : (false by default) adds more details to GC lgos

-XX:+PrintGCTimeStamps : adds timestamps to each GC timing

GC logs are the key piece of data required to diagnose GC issues; they should be collected routinely (even on production servers).

Chapter 6. Garbage Collection Algorithms

Adaptive and Static Heap Size Tuning

As the heap size is increased, the throughput rapidly increases—until the heap size is set to 1,500 MB

上のグラフはadaptive sizingがdisableになっている。

1500MB heap sizeが常に正解ではないだろうけど、Heapを増やすこと得られるPerformance Gainはどのアプリも上限値がある。HeapサイズのSweet Spotを探す時参考になりそう。Heapを増やしすぎてTPSが落ちるのはGC時のPause-timeが増えるせい。

 

Adaptive sizing in the throughput collector will resize the heap (and the generations) in order to meet its pause-time goals. Those goals are set with these flags: -XX:MaxGCPauseMillis=N and -XX:GCTimeRatio=N

役に立ちそうなコマンドなのでメモ。MaxGCPauseMillisの値が小さくなれば必然とHeap Sizeも小さくなるリ、GC頻度も上がる。

GCTimeRatioはDefaultでは99が入っている、つまり99%の時間はアプリの処理に使って1%の時間はGCに使う、でもGCTimeRatioはPercentageではないので注意が必要。

 

 

Tuning G1 GC

concurrent marking of G1 GC to be in a race with the application threads: G1 GC must clear out the old generation faster than the application is promoting new data into it.

G1 GCのメモリー解放スピードがアプリがメモリー書き込みスピードに追いつけない場合、-XX:ParallelGCThreads= N の値を増やすことFull GCを回避することができる。ただしCPUリソースを使うのでFree Lunchではない。

 

G1 GC can also win its race if it starts the background marking cycle earlier. That cycle begins when the heap hits the occupancy ratio specified by -XX:InitiatingHeapOccupancyPercent=N, which has a default value of 45.

InitiatingHeapOccupancyPercet この値のPercentはHeap全体のPercent. この値でC1 GCが走る頻度を決めることができる。値が小さ過ぎればFull GCを回避できる確率は増えるが、GC頻度が高くなりシステム全体のパフォーマンスは劣化する。なのでFull GCが起きない最大の値を設定するのがベスト。

 

 

Java Performance, 2nd Edition by Scott Oaks ch7 heap memory, ch8 native memory

Chapter 7. Heap Memory Best Practices

We have two conflicting goals here. The first general rule is to create objects sparingly and to discard them as quickly as possible. Using less memory is the best way to improve the efficiency of the garbage collector. On the other hand, frequently re-creating some kinds of objects can lead to worse overall performance (even if GC performance improves)

この章の趣旨は、memoryとそれを片付けるGCの効率化とシステム全体のパフォーマンスを向上する方法がまとめられてる。

 

Heap Histograms

% jmap -histo process_id

このコマンドでJavaアプリのheapの使用状況がわかる。覚えておこう

使用例

%  jmap -histo 21386

 num     #instances         #bytes  class name
----------------------------------------------
   1:         21019      152664296  [I
   2:         69898       23376176  [B
   3:        152345       18413344  [C
   4:         95700        2296800  java.lang.String
   5:         18177        1599576  java.lang.reflect.Method
   6:         24864        1455320  [Ljava.lang.Object;
   7:         33657        1346280  java.util.LinkedHashMap$Entry
   8:         63296        1316584  [Ljava.lang.Class;
   9:         17730        1230464  [Ljava.util.HashMap$Node;
  10:          9409        1049080  java.lang.Class

I = int

B = byte

C = char

reference : https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName()

 

Out-of-Memory Errors

主に原因は3種類

  • Out of native memory 

    • アプリケーション以外の要因で発生する可能性がある。
  • Out of metaspace memory

    • heap外の領域でデフォルトではmaximum sizeは設定されていない。任意にmaximum sizeが設定されている場合にのみ起こりうる。
    • 考えられる原因は二つ、シンプルにクラスパスで定義されているクラスの量が設定されているmaximum metaspace sizeを超えてる。又は、古いClassLoaderによloadされたクラスが残ってしまってメモリーを圧迫する。
  • Out-of-heap memory

    • objectsがGCに回収されるペースがobjectsが作られるスピードに追いつかなくてmemory leak が発生する。

どのケースでもheap dumpもしくはmemory histogramで解析するの有効手段。

 

Reducing Object Size

The size of an object can be decreased by (obviously) reducing the number of instance variables it holds and (less obviously) by reducing the size of those variables. 

 

Object Reuse

objects that are reused stay around for a long time in the heap. If the heap has a lot of objects, there is less room to create new objects, and hence GC operations will occur more frequently.

The length of time it takes to perform a full GC is proportional to the number of objects that are still alive in the old generation.

Objectの再利用によってパフォーマンスが向上するケースは限定的。connection pool やinitializeコストが大きいオブジェクトをpoolingして再利用するのは有効。

オブジェクトの再利用が推奨されない理由は

  1. オブジェクトが長い間Heapを占有するとHeap の利用可能領域が少なくなりGCが走る頻度が高くなる。
  2. オブジェクトがHeapに格納される間は eden -> survivor space -> old generation の順番で時間がたつと違う領域に移される。その領域を移動するプロセスがコスト。
  3. Full GCはalive objectがheapに多く残っている場合の方がパフォーマンスが悪い。

 

Chapter 8. Native Memory Best Practices

If enough physical memory to contain the entire total footprint of an application is not available, performance may begin to suffer.

ここでmayをつかっているのはapplicationがnative memory を多く占有していてもそれらは起動時にのみ必要な.jarが占有していて、それらはvirtual memoryに移されてもperformanceに影響しない可能性があるため。

ちなみにfootprintとはJava processのheap+non heapが占有しているメモリーの量。

 

When we look at performance, only committed memory really matters: a performance problem never results from reserving too much memory.

reserve memory とは特定のプロセスのために確保されているメモリー領域ではなく、必要に応じてOSが振り分けるメモリー領域の最大値。

 

Native Memory Tracking

% jcmd process_id VM.native_memory summary

このコマンドでnative memory の内訳が見れる。

見れるのはheap, Metaspace, thread stacks, GC, Native librariy allocationなどそれぞれのメモリー使用量がわかる。

  • If an application seems to be using too much native memory, it is likely from native libraries rather than the JVM itself.

なるほど〜。

Systems Performance 2nd Edition By Brendan Gregg Chapter 3 and 5

chapter 3 Operating System

3.2.1 Kernel

Unix-like operating systems including Linux and BSD have a monolithic kernel that manages CPU scheduling, memory, file systems, network protocols, and system devices

KernelはCPU, memory, file systemのドライバー的存在。したの図で表してるとおりApplicationsはSystem Librariesを介さなくもSystem Callsをよべる。JavaでいうとJNA(Java Native Access)機能など。

f:id:JunNguyen:20220130233823p:plain

3.2.2 Kernel and User Modes

In a traditional kernel, a system call is performed by switching to kernel mode and then executing the system call code.

Switching between user and kernel modes is a mode switch.

f:id:JunNguyen:20220206153523p:plain

mode switch はcontext switche 同様、CPU overheadは発生する。

Since mode and context switches cost a small amount of overhead (CPU cycles),3 there are various optimizations to avoid them, including:

  • User-mode syscalls: It is possible to implement some syscalls in a user-mode library alone. The Linux kernel does this by exporting a virtual dynamic shared object (vDSO) that is mapped into the process address space, which contains syscalls such as gettimeofday(2) and getcpu(2) [Drysdale 14].

  • Kernel bypass: This allows user-mode programs to access devices directly, bypassing syscalls and the typical kernel code path. For example, DPDK for networking: the Data Plane Development Kit.

system call は基本的にkernel mode で呼ばれるが、karnel mode とuser mode 間のswitching (mode switch) を最小にするための機能がkernel には備わってる。

 

3.2.9 Schedulers

A commonly used scheduling policy dating back to UNIX identifies CPU-bound workloads and decreases their priority, allowing I/O-bound workloads—where low-latency responses are more desirable—to run sooner.

時間がかかるCPU-bound workloadsを後回しにするのか、意外。

 

3.4 Linux

Linux kernel developments, especially those related to performance, include the following

  • CPU scheduling classes: Various advanced CPU scheduling algorithms have been developed, including scheduling domains (2.6.7) to make better decisions regarding non-uniform memory access (NUMA)
  • TCP congestion algorithms: Linux allows different TCP congestion control algorithms to be configured, and supports Reno, Cubic, and more in later kernels mentioned in this list.
  • splice (2.6.17): A system call to move data quickly between file descriptors and pipes, without a trip through user-space.

and many others...

Linuxは初代からnetwork, CPU, file managerなど様々角度からのperformance 改善が施されている。

 

Chapter 5. Applications

For application performance, you can start with what operations the application performs (as described earlier) and what the goal for performance is. The goal may be:

  • Latency: A low or consistent application response time
  • Throughput: A high application operation rate or data transfer rate
  • Resource utilization: Efficiency for a given application workload
  • Price: Improving the performance/price ratio, lowering computing costs

まずはゴールを設定するのが大事。response timeを上げるだけがperformance tuning ではない。

 

5.1.2 Optimize the Common Case

One way to efficiently improve application performance is to find the most common code path for the production workload and begin by improving that. 

あたりまえだけど、大事なところ。

 

5.2.2 Caching

Instead of always performing an expensive operation, the results of commonly performed operations may be stored in a local cache for future use. An example is the database buffer cache

Tuning the Database Buffer Cache

Oracle の Buffer Cacheについてもっと理解を深められたら、実業務で役立つ機会がありそう。

 

5.2.5 Concurrency and Parallelism

Fibers: Also called lightweight threads, these are a user-mode version of threads where each fiber represents a schedulable program. The application can use its own scheduling logic to choose which fiber to run.

Java ではThreadクラスをつかって非同期処理などを行えたけど、現在OpenJDK communityによってProject Loomという名前でSchedulerを開発者の任意でいじれるThreadクラスを現在開発されている。

 

 

 

Systems Performance 2nd Edition By Brendan Gregg

Chapter 1. Introduction

1.5.1 Subjectivity

Performance, on the other hand, is often subjective. With performance issues, it can be unclear whether there is an issue to begin with, and if so, when it has been fixed. What may be considered “bad” performance for one user, and therefore an issue, may be considered “good” performance for another.

答えがSubjectiveなものは確かにgood/badの判断が難しい。だからPerformance Tuningするときは定量的なゴールが必要。

1.7.2 Profiling

An effective visualization of CPU profiles is flame graphs. CPU flame graphs can help you find more performance wins than any other tool, after metrics.

flame graphsなんてものがあるのか、初めて聞いた。

perfコマンドで作れる。

CPU Flame Graphs

f:id:JunNguyen:20211206204312p:plain

 

2.1 Terminology

  • Bottleneck: In systems performance, a bottleneck is a resource that limits the performance of the system. Identifying and removing systemic bottlenecks is a key activity of systems performance.

パフォーマンス改善というと、手の付けやすそうな所から改善していくけどそこが本当にBottleneckなのかはきちんと調べないといけないなー。

 

2.2.1 System Under Test

The performance of a system under test (SUT) is shown in Figure 2.1.

Images

Figure 2.1 Block diagram of system under test

It is important to be aware that perturbations (interference) can affect results, including those caused by scheduled system activity, other users of the system, and other workloads. 

Pertubations: 辞書には動揺とか混乱とかいてあるけど、この本ではパフォーマンステストする際のシステムにかかるWorkload以外の負荷の事らしい。

本番環境でパフォーマンステストする際はデータソースはどうしてもPertubatonsを受けてしまうから、ちゃんと考慮しないといけないな。

 

2.3.6 When to Stop Analysis

When the potential ROI is less than the cost of analysis. Some performance issues I work on can deliver wins measured in tens of millions of dollars per year. For these I can justify spending months of my own time (engineering cost) on analysis. 

自分の時間(eninering cost)と成果物が釣り合ってるのかもっと考えないといけない。

 

2.3.9 Scalability

 

Images

Figure 2.7 Performance degradation

Higher response time is, of course, bad. The “fast” degradation profile may occur for memory load, when the system begins moving memory pages to disk to free main memory. The “slow” degradation profile may occur for CPU load.

なるほどー。覚えておこう。

Overhead

Performance metrics are not free; at some point, CPU cycles must be spent to gather and store them. This causes overhead, which can negatively affect the performance of the target of measurement. This is called the observer effect.

二重スリット実験を思い出す。見るとはpassiveではなくactive.

 

2.3.15 Known-Unknowns

  • Known-knowns: These are things you know. You know you should be checking a performance metric, and you know its current value. For example, you know you should be checking CPU utilization, and you also know that the value is 10% on average.

  • Known-unknowns: These are things you know that you do not know. You know you can check a metric or the existence of a subsystem, but you haven’t yet observed it. For example, you know you could use profiling to check what is making the CPUs busy, but have yet to do so.

  • Unknown-unknowns: These are things you do not know that you do not know. For example, you may not know that device interrupts can become heavy CPU consumers, so you are not checking them

この本を読むだけじゃなくて、ちゃんと会得してUnknow-unknownsをもっと減らしたい。