8013895: G1: G1SummarizeRSetStats output on Linux needs improvemen

Tue, 28 May 2013 09:32:06 +0200

author
tschatzl
date
Tue, 28 May 2013 09:32:06 +0200
changeset 5204
e72f7eecc96d
parent 5203
c20186fa611b
child 5205
3a4805ad0005
child 5206
87c64c0438fb

8013895: G1: G1SummarizeRSetStats output on Linux needs improvemen
Summary: Fixed the output of G1SummarizeRSetStats: too small datatype for the number of concurrently processed cards, added concurrent remembered set thread time retrieval for Linux and Windows (BSD uses os::elapsedTime() now), and other cleanup. The information presented during VM operation is now relative to the previous output, not always cumulative if G1SummarizeRSetStatsPeriod > 0. At VM exit, the code prints a cumulative summary.
Reviewed-by: johnc, jwilhelm

make/excludeSrc.make file | annotate | diff | comparison | revisions
src/os/bsd/vm/os_bsd.cpp file | annotate | diff | comparison | revisions
src/os/linux/vm/os_linux.cpp file | annotate | diff | comparison | revisions
src/os/windows/vm/os_windows.cpp file | annotate | diff | comparison | revisions
src/share/vm/gc_implementation/g1/concurrentG1Refine.cpp file | annotate | diff | comparison | revisions
src/share/vm/gc_implementation/g1/concurrentG1Refine.hpp file | annotate | diff | comparison | revisions
src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp file | annotate | diff | comparison | revisions
src/share/vm/gc_implementation/g1/g1RemSet.cpp file | annotate | diff | comparison | revisions
src/share/vm/gc_implementation/g1/g1RemSet.hpp file | annotate | diff | comparison | revisions
src/share/vm/gc_implementation/g1/g1RemSetSummary.cpp file | annotate | diff | comparison | revisions
src/share/vm/gc_implementation/g1/g1RemSetSummary.hpp file | annotate | diff | comparison | revisions
test/gc/g1/TestSummarizeRSetStats.java file | annotate | diff | comparison | revisions
     1.1 --- a/make/excludeSrc.make	Sat Jun 01 10:00:56 2013 +0200
     1.2 +++ b/make/excludeSrc.make	Tue May 28 09:32:06 2013 +0200
     1.3 @@ -87,7 +87,7 @@
     1.4  	g1BlockOffsetTable.cpp g1CardCounts.cpp g1CollectedHeap.cpp g1CollectorPolicy.cpp \
     1.5  	g1ErgoVerbose.cpp g1GCPhaseTimes.cpp g1HRPrinter.cpp g1HotCardCache.cpp g1Log.cpp \
     1.6  	g1MMUTracker.cpp g1MarkSweep.cpp g1MemoryPool.cpp g1MonitoringSupport.cpp \
     1.7 -	g1RemSet.cpp g1SATBCardTableModRefBS.cpp g1_globals.cpp heapRegion.cpp \
     1.8 +	g1RemSet.cpp g1RemSetSummary.cpp g1SATBCardTableModRefBS.cpp g1_globals.cpp heapRegion.cpp \
     1.9  	heapRegionRemSet.cpp heapRegionSeq.cpp heapRegionSet.cpp heapRegionSets.cpp \
    1.10  	ptrQueue.cpp satbQueue.cpp sparsePRT.cpp survRateGroup.cpp vm_operations_g1.cpp \
    1.11  	adjoiningGenerations.cpp adjoiningVirtualSpaces.cpp asPSOldGen.cpp asPSYoungGen.cpp \
     2.1 --- a/src/os/bsd/vm/os_bsd.cpp	Sat Jun 01 10:00:56 2013 +0200
     2.2 +++ b/src/os/bsd/vm/os_bsd.cpp	Tue May 28 09:32:06 2013 +0200
     2.3 @@ -935,10 +935,10 @@
     2.4    return (1000 * 1000);
     2.5  }
     2.6  
     2.7 -// XXX: For now, code this as if BSD does not support vtime.
     2.8 -bool os::supports_vtime() { return false; }
     2.9 +bool os::supports_vtime() { return true; }
    2.10  bool os::enable_vtime()   { return false; }
    2.11  bool os::vtime_enabled()  { return false; }
    2.12 +
    2.13  double os::elapsedVTime() {
    2.14    // better than nothing, but not much
    2.15    return elapsedTime();
     3.1 --- a/src/os/linux/vm/os_linux.cpp	Sat Jun 01 10:00:56 2013 +0200
     3.2 +++ b/src/os/linux/vm/os_linux.cpp	Tue May 28 09:32:06 2013 +0200
     3.3 @@ -101,6 +101,12 @@
     3.4  # include <inttypes.h>
     3.5  # include <sys/ioctl.h>
     3.6  
     3.7 +// if RUSAGE_THREAD for getrusage() has not been defined, do it here. The code calling
     3.8 +// getrusage() is prepared to handle the associated failure.
     3.9 +#ifndef RUSAGE_THREAD
    3.10 +#define RUSAGE_THREAD   (1)               /* only the calling thread */
    3.11 +#endif
    3.12 +
    3.13  #define MAX_PATH    (2 * K)
    3.14  
    3.15  // for timer info max values which include all bits
    3.16 @@ -1336,15 +1342,19 @@
    3.17    return (1000 * 1000);
    3.18  }
    3.19  
    3.20 -// For now, we say that linux does not support vtime.  I have no idea
    3.21 -// whether it can actually be made to (DLD, 9/13/05).
    3.22 -
    3.23 -bool os::supports_vtime() { return false; }
    3.24 +bool os::supports_vtime() { return true; }
    3.25  bool os::enable_vtime()   { return false; }
    3.26  bool os::vtime_enabled()  { return false; }
    3.27 +
    3.28  double os::elapsedVTime() {
    3.29 -  // better than nothing, but not much
    3.30 -  return elapsedTime();
    3.31 +  struct rusage usage;
    3.32 +  int retval = getrusage(RUSAGE_THREAD, &usage);
    3.33 +  if (retval == 0) {
    3.34 +    return (double) (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) + (double) (usage.ru_utime.tv_usec + usage.ru_stime.tv_usec) / (1000 * 1000);
    3.35 +  } else {
    3.36 +    // better than nothing, but not much
    3.37 +    return elapsedTime();
    3.38 +  }
    3.39  }
    3.40  
    3.41  jlong os::javaTimeMillis() {
     4.1 --- a/src/os/windows/vm/os_windows.cpp	Sat Jun 01 10:00:56 2013 +0200
     4.2 +++ b/src/os/windows/vm/os_windows.cpp	Tue May 28 09:32:06 2013 +0200
     4.3 @@ -813,15 +813,21 @@
     4.4    return result;
     4.5  }
     4.6  
     4.7 -// For now, we say that Windows does not support vtime.  I have no idea
     4.8 -// whether it can actually be made to (DLD, 9/13/05).
     4.9 -
    4.10 -bool os::supports_vtime() { return false; }
    4.11 +bool os::supports_vtime() { return true; }
    4.12  bool os::enable_vtime() { return false; }
    4.13  bool os::vtime_enabled() { return false; }
    4.14 +
    4.15  double os::elapsedVTime() {
    4.16 -  // better than nothing, but not much
    4.17 -  return elapsedTime();
    4.18 +  FILETIME created;
    4.19 +  FILETIME exited;
    4.20 +  FILETIME kernel;
    4.21 +  FILETIME user;
    4.22 +  if (GetThreadTimes(GetCurrentThread(), &created, &exited, &kernel, &user) != 0) {
    4.23 +    // the resolution of windows_to_java_time() should be sufficient (ms)
    4.24 +    return (double) (windows_to_java_time(kernel) + windows_to_java_time(user)) / MILLIUNITS;
    4.25 +  } else {
    4.26 +    return elapsedTime();
    4.27 +  }
    4.28  }
    4.29  
    4.30  jlong os::javaTimeMillis() {
     5.1 --- a/src/share/vm/gc_implementation/g1/concurrentG1Refine.cpp	Sat Jun 01 10:00:56 2013 +0200
     5.2 +++ b/src/share/vm/gc_implementation/g1/concurrentG1Refine.cpp	Tue May 28 09:32:06 2013 +0200
     5.3 @@ -114,6 +114,14 @@
     5.4    }
     5.5  }
     5.6  
     5.7 +void ConcurrentG1Refine::worker_threads_do(ThreadClosure * tc) {
     5.8 +  if (_threads != NULL) {
     5.9 +    for (int i = 0; i < worker_thread_num(); i++) {
    5.10 +      tc->do_thread(_threads[i]);
    5.11 +    }
    5.12 +  }
    5.13 +}
    5.14 +
    5.15  int ConcurrentG1Refine::thread_num() {
    5.16    int n_threads = (G1ConcRefinementThreads > 0) ? G1ConcRefinementThreads
    5.17                                                  : ParallelGCThreads;
    5.18 @@ -126,3 +134,7 @@
    5.19      st->cr();
    5.20    }
    5.21  }
    5.22 +
    5.23 +ConcurrentG1RefineThread * ConcurrentG1Refine::sampling_thread() const {
    5.24 +  return _threads[worker_thread_num()];
    5.25 +}
     6.1 --- a/src/share/vm/gc_implementation/g1/concurrentG1Refine.hpp	Sat Jun 01 10:00:56 2013 +0200
     6.2 +++ b/src/share/vm/gc_implementation/g1/concurrentG1Refine.hpp	Tue May 28 09:32:06 2013 +0200
     6.3 @@ -35,6 +35,7 @@
     6.4  class G1CollectedHeap;
     6.5  class G1HotCardCache;
     6.6  class G1RemSet;
     6.7 +class DirtyCardQueue;
     6.8  
     6.9  class ConcurrentG1Refine: public CHeapObj<mtGC> {
    6.10    ConcurrentG1RefineThread** _threads;
    6.11 @@ -78,9 +79,15 @@
    6.12  
    6.13    void reinitialize_threads();
    6.14  
    6.15 -  // Iterate over the conc refine threads
    6.16 +  // Iterate over all concurrent refinement threads
    6.17    void threads_do(ThreadClosure *tc);
    6.18  
    6.19 +  // Iterate over all worker refinement threads
    6.20 +  void worker_threads_do(ThreadClosure * tc);
    6.21 +
    6.22 +  // The RS sampling thread
    6.23 +  ConcurrentG1RefineThread * sampling_thread() const;
    6.24 +
    6.25    static int thread_num();
    6.26  
    6.27    void print_worker_threads_on(outputStream* st) const;
     7.1 --- a/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp	Sat Jun 01 10:00:56 2013 +0200
     7.2 +++ b/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp	Tue May 28 09:32:06 2013 +0200
     7.3 @@ -3539,6 +3539,14 @@
     7.4  }
     7.5  
     7.6  void G1CollectedHeap::gc_epilogue(bool full /* Ignored */) {
     7.7 +
     7.8 +  if (G1SummarizeRSetStats &&
     7.9 +      (G1SummarizeRSetStatsPeriod > 0) &&
    7.10 +      // we are at the end of the GC. Total collections has already been increased.
    7.11 +      ((total_collections() - 1) % G1SummarizeRSetStatsPeriod == 0)) {
    7.12 +    g1_rem_set()->print_periodic_summary_info();
    7.13 +  }
    7.14 +
    7.15    // FIXME: what is this about?
    7.16    // I'm ignoring the "fill_newgen()" call if "alloc_event_enabled"
    7.17    // is set.
    7.18 @@ -4093,12 +4101,6 @@
    7.19      g1mm()->update_sizes();
    7.20    }
    7.21  
    7.22 -  if (G1SummarizeRSetStats &&
    7.23 -      (G1SummarizeRSetStatsPeriod > 0) &&
    7.24 -      (total_collections() % G1SummarizeRSetStatsPeriod == 0)) {
    7.25 -    g1_rem_set()->print_summary_info();
    7.26 -  }
    7.27 -
    7.28    // It should now be safe to tell the concurrent mark thread to start
    7.29    // without its logging output interfering with the logging output
    7.30    // that came from the pause.
     8.1 --- a/src/share/vm/gc_implementation/g1/g1RemSet.cpp	Sat Jun 01 10:00:56 2013 +0200
     8.2 +++ b/src/share/vm/gc_implementation/g1/g1RemSet.cpp	Tue May 28 09:32:06 2013 +0200
     8.3 @@ -34,6 +34,7 @@
     8.4  #include "gc_implementation/g1/g1OopClosures.inline.hpp"
     8.5  #include "gc_implementation/g1/g1RemSet.inline.hpp"
     8.6  #include "gc_implementation/g1/heapRegionSeq.inline.hpp"
     8.7 +#include "gc_implementation/g1/heapRegionRemSet.hpp"
     8.8  #include "memory/iterator.hpp"
     8.9  #include "oops/oop.inline.hpp"
    8.10  #include "utilities/intHisto.hpp"
    8.11 @@ -73,7 +74,8 @@
    8.12      _ct_bs(ct_bs), _g1p(_g1->g1_policy()),
    8.13      _cg1r(g1->concurrent_g1_refine()),
    8.14      _cset_rs_update_cl(NULL),
    8.15 -    _cards_scanned(NULL), _total_cards_scanned(0)
    8.16 +    _cards_scanned(NULL), _total_cards_scanned(0),
    8.17 +    _prev_period_summary()
    8.18  {
    8.19    _seq_task = new SubTasksDone(NumSeqTasks);
    8.20    guarantee(n_workers() > 0, "There should be some workers");
    8.21 @@ -81,6 +83,7 @@
    8.22    for (uint i = 0; i < n_workers(); i++) {
    8.23      _cset_rs_update_cl[i] = NULL;
    8.24    }
    8.25 +  _prev_period_summary.initialize(this, n_workers());
    8.26  }
    8.27  
    8.28  G1RemSet::~G1RemSet() {
    8.29 @@ -697,47 +700,29 @@
    8.30    return has_refs_into_cset;
    8.31  }
    8.32  
    8.33 -class HRRSStatsIter: public HeapRegionClosure {
    8.34 -  size_t _occupied;
    8.35 -  size_t _total_mem_sz;
    8.36 -  size_t _max_mem_sz;
    8.37 -  HeapRegion* _max_mem_sz_region;
    8.38 -public:
    8.39 -  HRRSStatsIter() :
    8.40 -    _occupied(0),
    8.41 -    _total_mem_sz(0),
    8.42 -    _max_mem_sz(0),
    8.43 -    _max_mem_sz_region(NULL)
    8.44 -  {}
    8.45 +void G1RemSet::print_periodic_summary_info() {
    8.46 +  G1RemSetSummary current;
    8.47 +  current.initialize(this, n_workers());
    8.48  
    8.49 -  bool doHeapRegion(HeapRegion* r) {
    8.50 -    if (r->continuesHumongous()) return false;
    8.51 -    size_t mem_sz = r->rem_set()->mem_size();
    8.52 -    if (mem_sz > _max_mem_sz) {
    8.53 -      _max_mem_sz = mem_sz;
    8.54 -      _max_mem_sz_region = r;
    8.55 -    }
    8.56 -    _total_mem_sz += mem_sz;
    8.57 -    size_t occ = r->rem_set()->occupied();
    8.58 -    _occupied += occ;
    8.59 -    return false;
    8.60 -  }
    8.61 -  size_t total_mem_sz() { return _total_mem_sz; }
    8.62 -  size_t max_mem_sz() { return _max_mem_sz; }
    8.63 -  size_t occupied() { return _occupied; }
    8.64 -  HeapRegion* max_mem_sz_region() { return _max_mem_sz_region; }
    8.65 -};
    8.66 +  _prev_period_summary.subtract_from(&current);
    8.67 +  print_summary_info(&_prev_period_summary);
    8.68  
    8.69 -class PrintRSThreadVTimeClosure : public ThreadClosure {
    8.70 -public:
    8.71 -  virtual void do_thread(Thread *t) {
    8.72 -    ConcurrentG1RefineThread* crt = (ConcurrentG1RefineThread*) t;
    8.73 -    gclog_or_tty->print("    %5.2f", crt->vtime_accum());
    8.74 -  }
    8.75 -};
    8.76 +  _prev_period_summary.set(&current);
    8.77 +}
    8.78  
    8.79  void G1RemSet::print_summary_info() {
    8.80 -  G1CollectedHeap* g1 = G1CollectedHeap::heap();
    8.81 +  G1RemSetSummary current;
    8.82 +  current.initialize(this, n_workers());
    8.83 +
    8.84 +  print_summary_info(&current, " Cumulative RS summary");
    8.85 +}
    8.86 +
    8.87 +void G1RemSet::print_summary_info(G1RemSetSummary * summary, const char * header) {
    8.88 +  assert(summary != NULL, "just checking");
    8.89 +
    8.90 +  if (header != NULL) {
    8.91 +    gclog_or_tty->print_cr("%s", header);
    8.92 +  }
    8.93  
    8.94  #if CARD_REPEAT_HISTO
    8.95    gclog_or_tty->print_cr("\nG1 card_repeat count histogram: ");
    8.96 @@ -745,46 +730,7 @@
    8.97    card_repeat_count.print_on(gclog_or_tty);
    8.98  #endif
    8.99  
   8.100 -  gclog_or_tty->print_cr("\n Concurrent RS processed %d cards",
   8.101 -                         _conc_refine_cards);
   8.102 -  DirtyCardQueueSet& dcqs = JavaThread::dirty_card_queue_set();
   8.103 -  jint tot_processed_buffers =
   8.104 -    dcqs.processed_buffers_mut() + dcqs.processed_buffers_rs_thread();
   8.105 -  gclog_or_tty->print_cr("  Of %d completed buffers:", tot_processed_buffers);
   8.106 -  gclog_or_tty->print_cr("     %8d (%5.1f%%) by conc RS threads.",
   8.107 -                dcqs.processed_buffers_rs_thread(),
   8.108 -                100.0*(float)dcqs.processed_buffers_rs_thread()/
   8.109 -                (float)tot_processed_buffers);
   8.110 -  gclog_or_tty->print_cr("     %8d (%5.1f%%) by mutator threads.",
   8.111 -                dcqs.processed_buffers_mut(),
   8.112 -                100.0*(float)dcqs.processed_buffers_mut()/
   8.113 -                (float)tot_processed_buffers);
   8.114 -  gclog_or_tty->print_cr("  Conc RS threads times(s)");
   8.115 -  PrintRSThreadVTimeClosure p;
   8.116 -  gclog_or_tty->print("     ");
   8.117 -  g1->concurrent_g1_refine()->threads_do(&p);
   8.118 -  gclog_or_tty->print_cr("");
   8.119 -
   8.120 -  HRRSStatsIter blk;
   8.121 -  g1->heap_region_iterate(&blk);
   8.122 -  gclog_or_tty->print_cr("  Total heap region rem set sizes = "SIZE_FORMAT"K."
   8.123 -                         "  Max = "SIZE_FORMAT"K.",
   8.124 -                         blk.total_mem_sz()/K, blk.max_mem_sz()/K);
   8.125 -  gclog_or_tty->print_cr("  Static structures = "SIZE_FORMAT"K,"
   8.126 -                         " free_lists = "SIZE_FORMAT"K.",
   8.127 -                         HeapRegionRemSet::static_mem_size() / K,
   8.128 -                         HeapRegionRemSet::fl_mem_size() / K);
   8.129 -  gclog_or_tty->print_cr("    "SIZE_FORMAT" occupied cards represented.",
   8.130 -                         blk.occupied());
   8.131 -  HeapRegion* max_mem_sz_region = blk.max_mem_sz_region();
   8.132 -  HeapRegionRemSet* rem_set = max_mem_sz_region->rem_set();
   8.133 -  gclog_or_tty->print_cr("    Max size region = "HR_FORMAT", "
   8.134 -                         "size = "SIZE_FORMAT "K, occupied = "SIZE_FORMAT"K.",
   8.135 -                         HR_FORMAT_PARAMS(max_mem_sz_region),
   8.136 -                         (rem_set->mem_size() + K - 1)/K,
   8.137 -                         (rem_set->occupied() + K - 1)/K);
   8.138 -  gclog_or_tty->print_cr("    Did %d coarsenings.",
   8.139 -                         HeapRegionRemSet::n_coarsenings());
   8.140 +  summary->print_on(gclog_or_tty);
   8.141  }
   8.142  
   8.143  void G1RemSet::prepare_for_verify() {
     9.1 --- a/src/share/vm/gc_implementation/g1/g1RemSet.hpp	Sat Jun 01 10:00:56 2013 +0200
     9.2 +++ b/src/share/vm/gc_implementation/g1/g1RemSet.hpp	Tue May 28 09:32:06 2013 +0200
     9.3 @@ -25,6 +25,8 @@
     9.4  #ifndef SHARE_VM_GC_IMPLEMENTATION_G1_G1REMSET_HPP
     9.5  #define SHARE_VM_GC_IMPLEMENTATION_G1_G1REMSET_HPP
     9.6  
     9.7 +#include "gc_implementation/g1/g1RemSetSummary.hpp"
     9.8 +
     9.9  // A G1RemSet provides ways of iterating over pointers into a selected
    9.10  // collection set.
    9.11  
    9.12 @@ -37,9 +39,11 @@
    9.13  // so that they can be used to update the individual region remsets.
    9.14  
    9.15  class G1RemSet: public CHeapObj<mtGC> {
    9.16 +private:
    9.17 +  G1RemSetSummary _prev_period_summary;
    9.18  protected:
    9.19    G1CollectedHeap* _g1;
    9.20 -  unsigned _conc_refine_cards;
    9.21 +  size_t _conc_refine_cards;
    9.22    uint n_workers();
    9.23  
    9.24  protected:
    9.25 @@ -66,6 +70,8 @@
    9.26    // references into the collection set.
    9.27    OopsInHeapRegionClosure** _cset_rs_update_cl;
    9.28  
    9.29 +  // Print the given summary info
    9.30 +  virtual void print_summary_info(G1RemSetSummary * summary, const char * header = NULL);
    9.31  public:
    9.32    // This is called to reset dual hash tables after the gc pause
    9.33    // is finished and the initial hash table is no longer being
    9.34 @@ -123,11 +129,18 @@
    9.35                             int worker_i,
    9.36                             bool check_for_refs_into_cset);
    9.37  
    9.38 -  // Print any relevant summary info.
    9.39 +  // Print accumulated summary info from the start of the VM.
    9.40    virtual void print_summary_info();
    9.41  
    9.42 +  // Print accumulated summary info from the last time called.
    9.43 +  virtual void print_periodic_summary_info();
    9.44 +
    9.45    // Prepare remembered set for verification.
    9.46    virtual void prepare_for_verify();
    9.47 +
    9.48 +  size_t conc_refine_cards() const {
    9.49 +    return _conc_refine_cards;
    9.50 +  }
    9.51  };
    9.52  
    9.53  class CountNonCleanMemRegionClosure: public MemRegionClosure {
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/src/share/vm/gc_implementation/g1/g1RemSetSummary.cpp	Tue May 28 09:32:06 2013 +0200
    10.3 @@ -0,0 +1,205 @@
    10.4 +/*
    10.5 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
    10.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    10.7 + *
    10.8 + * This code is free software; you can redistribute it and/or modify it
    10.9 + * under the terms of the GNU General Public License version 2 only, as
   10.10 + * published by the Free Software Foundation.
   10.11 + *
   10.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
   10.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   10.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   10.15 + * version 2 for more details (a copy is included in the LICENSE file that
   10.16 + * accompanied this code).
   10.17 + *
   10.18 + * You should have received a copy of the GNU General Public License version
   10.19 + * 2 along with this work; if not, write to the Free Software Foundation,
   10.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   10.21 + *
   10.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   10.23 + * or visit www.oracle.com if you need additional information or have any
   10.24 + * questions.
   10.25 + *
   10.26 + */
   10.27 +
   10.28 +#include "precompiled.hpp"
   10.29 +#include "gc_implementation/g1/concurrentG1Refine.hpp"
   10.30 +#include "gc_implementation/g1/concurrentG1RefineThread.hpp"
   10.31 +#include "gc_implementation/g1/heapRegion.hpp"
   10.32 +#include "gc_implementation/g1/g1CollectedHeap.inline.hpp"
   10.33 +#include "gc_implementation/g1/g1RemSet.inline.hpp"
   10.34 +#include "gc_implementation/g1/g1RemSetSummary.hpp"
   10.35 +#include "gc_implementation/g1/heapRegionRemSet.hpp"
   10.36 +#include "runtime/thread.inline.hpp"
   10.37 +
   10.38 +class GetRSThreadVTimeClosure : public ThreadClosure {
   10.39 +private:
   10.40 +  G1RemSetSummary* _summary;
   10.41 +  uint _counter;
   10.42 +
   10.43 +public:
   10.44 +  GetRSThreadVTimeClosure(G1RemSetSummary * summary) : ThreadClosure(), _summary(summary), _counter(0) {
   10.45 +    assert(_summary != NULL, "just checking");
   10.46 +  }
   10.47 +
   10.48 +  virtual void do_thread(Thread* t) {
   10.49 +    ConcurrentG1RefineThread* crt = (ConcurrentG1RefineThread*) t;
   10.50 +    _summary->set_rs_thread_vtime(_counter, crt->vtime_accum());
   10.51 +    _counter++;
   10.52 +  }
   10.53 +};
   10.54 +
   10.55 +void G1RemSetSummary::update() {
   10.56 +  _num_refined_cards = remset()->conc_refine_cards();
   10.57 +  DirtyCardQueueSet& dcqs = JavaThread::dirty_card_queue_set();
   10.58 +  _num_processed_buf_mutator = dcqs.processed_buffers_mut();
   10.59 +  _num_processed_buf_rs_threads = dcqs.processed_buffers_rs_thread();
   10.60 +
   10.61 +  _num_coarsenings = HeapRegionRemSet::n_coarsenings();
   10.62 +
   10.63 +  ConcurrentG1Refine * cg1r = G1CollectedHeap::heap()->concurrent_g1_refine();
   10.64 +  if (_rs_threads_vtimes != NULL) {
   10.65 +    GetRSThreadVTimeClosure p(this);
   10.66 +    cg1r->worker_threads_do(&p);
   10.67 +  }
   10.68 +  set_sampling_thread_vtime(cg1r->sampling_thread()->vtime_accum());
   10.69 +}
   10.70 +
   10.71 +void G1RemSetSummary::set_rs_thread_vtime(uint thread, double value) {
   10.72 +  assert(_rs_threads_vtimes != NULL, "just checking");
   10.73 +  assert(thread < _num_vtimes, "just checking");
   10.74 +  _rs_threads_vtimes[thread] = value;
   10.75 +}
   10.76 +
   10.77 +double G1RemSetSummary::rs_thread_vtime(uint thread) const {
   10.78 +  assert(_rs_threads_vtimes != NULL, "just checking");
   10.79 +  assert(thread < _num_vtimes, "just checking");
   10.80 +  return _rs_threads_vtimes[thread];
   10.81 +}
   10.82 +
   10.83 +void G1RemSetSummary::initialize(G1RemSet* remset, uint num_workers) {
   10.84 +  assert(_rs_threads_vtimes == NULL, "just checking");
   10.85 +  assert(remset != NULL, "just checking");
   10.86 +
   10.87 +  _remset = remset;
   10.88 +  _num_vtimes = num_workers;
   10.89 +  _rs_threads_vtimes = NEW_C_HEAP_ARRAY(double, _num_vtimes, mtGC);
   10.90 +  memset(_rs_threads_vtimes, 0, sizeof(double) * _num_vtimes);
   10.91 +
   10.92 +  update();
   10.93 +}
   10.94 +
   10.95 +void G1RemSetSummary::set(G1RemSetSummary* other) {
   10.96 +  assert(other != NULL, "just checking");
   10.97 +  assert(remset() == other->remset(), "just checking");
   10.98 +  assert(_num_vtimes == other->_num_vtimes, "just checking");
   10.99 +
  10.100 +  _num_refined_cards = other->num_concurrent_refined_cards();
  10.101 +
  10.102 +  _num_processed_buf_mutator = other->num_processed_buf_mutator();
  10.103 +  _num_processed_buf_rs_threads = other->num_processed_buf_rs_threads();
  10.104 +
  10.105 +  _num_coarsenings = other->_num_coarsenings;
  10.106 +
  10.107 +  memcpy(_rs_threads_vtimes, other->_rs_threads_vtimes, sizeof(double) * _num_vtimes);
  10.108 +
  10.109 +  set_sampling_thread_vtime(other->sampling_thread_vtime());
  10.110 +}
  10.111 +
  10.112 +void G1RemSetSummary::subtract_from(G1RemSetSummary* other) {
  10.113 +  assert(other != NULL, "just checking");
  10.114 +  assert(remset() == other->remset(), "just checking");
  10.115 +  assert(_num_vtimes == other->_num_vtimes, "just checking");
  10.116 +
  10.117 +  _num_refined_cards = other->num_concurrent_refined_cards() - _num_refined_cards;
  10.118 +
  10.119 +  _num_processed_buf_mutator = other->num_processed_buf_mutator() - _num_processed_buf_mutator;
  10.120 +  _num_processed_buf_rs_threads = other->num_processed_buf_rs_threads() - _num_processed_buf_rs_threads;
  10.121 +
  10.122 +  _num_coarsenings = other->num_coarsenings() - _num_coarsenings;
  10.123 +
  10.124 +  for (uint i = 0; i < _num_vtimes; i++) {
  10.125 +    set_rs_thread_vtime(i, other->rs_thread_vtime(i) - rs_thread_vtime(i));
  10.126 +  }
  10.127 +
  10.128 +  _sampling_thread_vtime = other->sampling_thread_vtime() - _sampling_thread_vtime;
  10.129 +}
  10.130 +
  10.131 +class HRRSStatsIter: public HeapRegionClosure {
  10.132 +  size_t _occupied;
  10.133 +  size_t _total_mem_sz;
  10.134 +  size_t _max_mem_sz;
  10.135 +  HeapRegion* _max_mem_sz_region;
  10.136 +public:
  10.137 +  HRRSStatsIter() :
  10.138 +    _occupied(0),
  10.139 +    _total_mem_sz(0),
  10.140 +    _max_mem_sz(0),
  10.141 +    _max_mem_sz_region(NULL)
  10.142 +  {}
  10.143 +
  10.144 +  bool doHeapRegion(HeapRegion* r) {
  10.145 +    size_t mem_sz = r->rem_set()->mem_size();
  10.146 +    if (mem_sz > _max_mem_sz) {
  10.147 +      _max_mem_sz = mem_sz;
  10.148 +      _max_mem_sz_region = r;
  10.149 +    }
  10.150 +    _total_mem_sz += mem_sz;
  10.151 +    size_t occ = r->rem_set()->occupied();
  10.152 +    _occupied += occ;
  10.153 +    return false;
  10.154 +  }
  10.155 +  size_t total_mem_sz() { return _total_mem_sz; }
  10.156 +  size_t max_mem_sz() { return _max_mem_sz; }
  10.157 +  size_t occupied() { return _occupied; }
  10.158 +  HeapRegion* max_mem_sz_region() { return _max_mem_sz_region; }
  10.159 +};
  10.160 +
  10.161 +double calc_percentage(size_t numerator, size_t denominator) {
  10.162 +  if (denominator != 0) {
  10.163 +    return (double)numerator / denominator * 100.0;
  10.164 +  } else {
  10.165 +    return 0.0f;
  10.166 +  }
  10.167 +}
  10.168 +
  10.169 +void G1RemSetSummary::print_on(outputStream* out) {
  10.170 +  out->print_cr("\n Concurrent RS processed "SIZE_FORMAT" cards",
  10.171 +                num_concurrent_refined_cards());
  10.172 +  out->print_cr("  Of %d completed buffers:", num_processed_buf_total());
  10.173 +  out->print_cr("     %8d (%5.1f%%) by concurrent RS threads.",
  10.174 +                num_processed_buf_total(),
  10.175 +                calc_percentage(num_processed_buf_rs_threads(), num_processed_buf_total()));
  10.176 +  out->print_cr("     %8d (%5.1f%%) by mutator threads.",
  10.177 +                num_processed_buf_mutator(),
  10.178 +                calc_percentage(num_processed_buf_mutator(), num_processed_buf_total()));
  10.179 +  out->print_cr("  Concurrent RS threads times (s)");
  10.180 +  out->print("     ");
  10.181 +  for (uint i = 0; i < _num_vtimes; i++) {
  10.182 +    out->print("    %5.2f", rs_thread_vtime(i));
  10.183 +  }
  10.184 +  out->cr();
  10.185 +  out->print_cr("  Concurrent sampling threads times (s)");
  10.186 +  out->print_cr("         %5.2f", sampling_thread_vtime());
  10.187 +
  10.188 +  HRRSStatsIter blk;
  10.189 +  G1CollectedHeap::heap()->heap_region_iterate(&blk);
  10.190 +  out->print_cr("  Total heap region rem set sizes = "SIZE_FORMAT"K."
  10.191 +                "  Max = "SIZE_FORMAT"K.",
  10.192 +                blk.total_mem_sz()/K, blk.max_mem_sz()/K);
  10.193 +  out->print_cr("  Static structures = "SIZE_FORMAT"K,"
  10.194 +                " free_lists = "SIZE_FORMAT"K.",
  10.195 +                HeapRegionRemSet::static_mem_size() / K,
  10.196 +                HeapRegionRemSet::fl_mem_size() / K);
  10.197 +  out->print_cr("    "SIZE_FORMAT" occupied cards represented.",
  10.198 +                blk.occupied());
  10.199 +  HeapRegion* max_mem_sz_region = blk.max_mem_sz_region();
  10.200 +  HeapRegionRemSet* rem_set = max_mem_sz_region->rem_set();
  10.201 +  out->print_cr("    Max size region = "HR_FORMAT", "
  10.202 +                "size = "SIZE_FORMAT "K, occupied = "SIZE_FORMAT"K.",
  10.203 +                HR_FORMAT_PARAMS(max_mem_sz_region),
  10.204 +                (rem_set->mem_size() + K - 1)/K,
  10.205 +                (rem_set->occupied() + K - 1)/K);
  10.206 +
  10.207 +  out->print_cr("    Did %d coarsenings.", num_coarsenings());
  10.208 +}
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/src/share/vm/gc_implementation/g1/g1RemSetSummary.hpp	Tue May 28 09:32:06 2013 +0200
    11.3 @@ -0,0 +1,118 @@
    11.4 +/*
    11.5 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
    11.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    11.7 + *
    11.8 + * This code is free software; you can redistribute it and/or modify it
    11.9 + * under the terms of the GNU General Public License version 2 only, as
   11.10 + * published by the Free Software Foundation.
   11.11 + *
   11.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
   11.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   11.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   11.15 + * version 2 for more details (a copy is included in the LICENSE file that
   11.16 + * accompanied this code).
   11.17 + *
   11.18 + * You should have received a copy of the GNU General Public License version
   11.19 + * 2 along with this work; if not, write to the Free Software Foundation,
   11.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   11.21 + *
   11.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   11.23 + * or visit www.oracle.com if you need additional information or have any
   11.24 + * questions.
   11.25 + *
   11.26 + */
   11.27 +
   11.28 +#ifndef SHARE_VM_GC_IMPLEMENTATION_G1_G1REMSETSUMMARY_HPP
   11.29 +#define SHARE_VM_GC_IMPLEMENTATION_G1_G1REMSETSUMMARY_HPP
   11.30 +
   11.31 +#include "utilities/ostream.hpp"
   11.32 +
   11.33 +class G1RemSet;
   11.34 +
   11.35 +// A G1RemSetSummary manages statistical information about the G1RemSet
   11.36 +
   11.37 +class G1RemSetSummary VALUE_OBJ_CLASS_SPEC {
   11.38 +private:
   11.39 +  friend class GetRSThreadVTimeClosure;
   11.40 +
   11.41 +  G1RemSet* _remset;
   11.42 +
   11.43 +  G1RemSet* remset() const {
   11.44 +    return _remset;
   11.45 +  }
   11.46 +
   11.47 +  size_t _num_refined_cards;
   11.48 +  size_t _num_processed_buf_mutator;
   11.49 +  size_t _num_processed_buf_rs_threads;
   11.50 +
   11.51 +  size_t _num_coarsenings;
   11.52 +
   11.53 +  double* _rs_threads_vtimes;
   11.54 +  size_t _num_vtimes;
   11.55 +
   11.56 +  double _sampling_thread_vtime;
   11.57 +
   11.58 +  void set_rs_thread_vtime(uint thread, double value);
   11.59 +  void set_sampling_thread_vtime(double value) {
   11.60 +    _sampling_thread_vtime = value;
   11.61 +  }
   11.62 +
   11.63 +  void free_and_null() {
   11.64 +    if (_rs_threads_vtimes) {
   11.65 +      FREE_C_HEAP_ARRAY(double, _rs_threads_vtimes, mtGC);
   11.66 +      _rs_threads_vtimes = NULL;
   11.67 +      _num_vtimes = 0;
   11.68 +    }
   11.69 +  }
   11.70 +
   11.71 +  // update this summary with current data from various places
   11.72 +  void update();
   11.73 +
   11.74 +public:
   11.75 +  G1RemSetSummary() : _remset(NULL), _num_refined_cards(0),
   11.76 +    _num_processed_buf_mutator(0), _num_processed_buf_rs_threads(0), _num_coarsenings(0),
   11.77 +    _rs_threads_vtimes(NULL), _num_vtimes(0), _sampling_thread_vtime(0.0f) {
   11.78 +  }
   11.79 +
   11.80 +  ~G1RemSetSummary() {
   11.81 +    free_and_null();
   11.82 +  }
   11.83 +
   11.84 +  // set the counters in this summary to the values of the others
   11.85 +  void set(G1RemSetSummary* other);
   11.86 +  // subtract all counters from the other summary, and set them in the current
   11.87 +  void subtract_from(G1RemSetSummary* other);
   11.88 +
   11.89 +  // initialize and get the first sampling
   11.90 +  void initialize(G1RemSet* remset, uint num_workers);
   11.91 +
   11.92 +  void print_on(outputStream* out);
   11.93 +
   11.94 +  double rs_thread_vtime(uint thread) const;
   11.95 +
   11.96 +  double sampling_thread_vtime() const {
   11.97 +    return _sampling_thread_vtime;
   11.98 +  }
   11.99 +
  11.100 +  size_t num_concurrent_refined_cards() const {
  11.101 +    return _num_refined_cards;
  11.102 +  }
  11.103 +
  11.104 +  size_t num_processed_buf_mutator() const {
  11.105 +    return _num_processed_buf_mutator;
  11.106 +  }
  11.107 +
  11.108 +  size_t num_processed_buf_rs_threads() const {
  11.109 +    return _num_processed_buf_rs_threads;
  11.110 +  }
  11.111 +
  11.112 +  size_t num_processed_buf_total() const {
  11.113 +    return num_processed_buf_mutator() + num_processed_buf_rs_threads();
  11.114 +  }
  11.115 +
  11.116 +  size_t num_coarsenings() const {
  11.117 +    return _num_coarsenings;
  11.118 +  }
  11.119 +};
  11.120 +
  11.121 +#endif // SHARE_VM_GC_IMPLEMENTATION_G1_G1REMSETSUMMARY_HPP
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/test/gc/g1/TestSummarizeRSetStats.java	Tue May 28 09:32:06 2013 +0200
    12.3 @@ -0,0 +1,164 @@
    12.4 +/*
    12.5 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
    12.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    12.7 + *
    12.8 + * This code is free software; you can redistribute it and/or modify it
    12.9 + * under the terms of the GNU General Public License version 2 only, as
   12.10 + * published by the Free Software Foundation.
   12.11 + *
   12.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
   12.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   12.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   12.15 + * version 2 for more details (a copy is included in the LICENSE file that
   12.16 + * accompanied this code).
   12.17 + *
   12.18 + * You should have received a copy of the GNU General Public License version
   12.19 + * 2 along with this work; if not, write to the Free Software Foundation,
   12.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   12.21 + *
   12.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   12.23 + * or visit www.oracle.com if you need additional information or have any
   12.24 + * questions.
   12.25 + */
   12.26 +
   12.27 +/*
   12.28 + * @test TestSummarizeRSetStats.java
   12.29 + * @bug 8013895
   12.30 + * @library /testlibrary
   12.31 + * @build TestSummarizeRSetStats
   12.32 + * @summary Verify output of -XX:+G1SummarizeRSetStats
   12.33 + * @run main TestSummarizeRSetStats
   12.34 + *
   12.35 + * Test the output of G1SummarizeRSetStats in conjunction with G1SummarizeRSetStatsPeriod.
   12.36 + */
   12.37 +
   12.38 +import com.oracle.java.testlibrary.*;
   12.39 +import java.lang.Thread;
   12.40 +import java.util.ArrayList;
   12.41 +import java.util.Arrays;
   12.42 +
   12.43 +class RunSystemGCs {
   12.44 +    // 4M size, both are directly allocated into the old gen
   12.45 +    static Object[] largeObject1 = new Object[1024 * 1024];
   12.46 +    static Object[] largeObject2 = new Object[1024 * 1024];
   12.47 +
   12.48 +    static int[] temp;
   12.49 +
   12.50 +    public static void main(String[] args) {
   12.51 +        // create some cross-references between these objects
   12.52 +        for (int i = 0; i < largeObject1.length; i++) {
   12.53 +            largeObject1[i] = largeObject2;
   12.54 +        }
   12.55 +
   12.56 +        for (int i = 0; i < largeObject2.length; i++) {
   12.57 +            largeObject2[i] = largeObject1;
   12.58 +        }
   12.59 +
   12.60 +        int numGCs = Integer.parseInt(args[0]);
   12.61 +
   12.62 +        if (numGCs > 0) {
   12.63 +            // try to force a minor collection: the young gen is 4M, the
   12.64 +            // amount of data allocated below is roughly that (4*1024*1024 +
   12.65 +            // some header data)
   12.66 +            for (int i = 0; i < 1024 ; i++) {
   12.67 +                temp = new int[1024];
   12.68 +            }
   12.69 +        }
   12.70 +
   12.71 +        for (int i = 0; i < numGCs - 1; i++) {
   12.72 +            System.gc();
   12.73 +        }
   12.74 +    }
   12.75 +}
   12.76 +
   12.77 +public class TestSummarizeRSetStats {
   12.78 +
   12.79 +    public static String runTest(String[] additionalArgs, int numGCs) throws Exception {
   12.80 +        ArrayList<String> finalargs = new ArrayList<String>();
   12.81 +        String[] defaultArgs = new String[] {
   12.82 +            "-XX:+UseG1GC",
   12.83 +            "-Xmn4m",
   12.84 +            "-Xmx20m",
   12.85 +            "-XX:InitiatingHeapOccupancyPercent=100", // we don't want the additional GCs due to initial marking
   12.86 +            "-XX:+PrintGC",
   12.87 +            "-XX:+UnlockDiagnosticVMOptions",
   12.88 +            "-XX:G1HeapRegionSize=1M",
   12.89 +        };
   12.90 +
   12.91 +        finalargs.addAll(Arrays.asList(defaultArgs));
   12.92 +
   12.93 +        if (additionalArgs != null) {
   12.94 +            finalargs.addAll(Arrays.asList(additionalArgs));
   12.95 +        }
   12.96 +
   12.97 +        finalargs.add(RunSystemGCs.class.getName());
   12.98 +        finalargs.add(String.valueOf(numGCs));
   12.99 +
  12.100 +        ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
  12.101 +            finalargs.toArray(new String[0]));
  12.102 +        OutputAnalyzer output = new OutputAnalyzer(pb.start());
  12.103 +
  12.104 +        output.shouldHaveExitValue(0);
  12.105 +
  12.106 +        String result = output.getStdout();
  12.107 +        return result;
  12.108 +    }
  12.109 +
  12.110 +    private static void expectStatistics(String result, int expectedCumulative, int expectedPeriodic) throws Exception {
  12.111 +        int actualTotal = result.split("Concurrent RS processed").length - 1;
  12.112 +        int actualCumulative = result.split("Cumulative RS summary").length - 1;
  12.113 +
  12.114 +        if (expectedCumulative != actualCumulative) {
  12.115 +            throw new Exception("Incorrect amount of RSet summaries at the end. Expected " + expectedCumulative + ", got " + actualCumulative);
  12.116 +        }
  12.117 +
  12.118 +        if (expectedPeriodic != (actualTotal - actualCumulative)) {
  12.119 +            throw new Exception("Incorrect amount of per-period RSet summaries at the end. Expected " + expectedPeriodic + ", got " + (actualTotal - actualCumulative));
  12.120 +        }
  12.121 +    }
  12.122 +
  12.123 +    public static void main(String[] args) throws Exception {
  12.124 +        String result;
  12.125 +
  12.126 +        // no RSet statistics output
  12.127 +        result = runTest(null, 0);
  12.128 +        expectStatistics(result, 0, 0);
  12.129 +
  12.130 +        // no RSet statistics output
  12.131 +        result = runTest(null, 2);
  12.132 +        expectStatistics(result, 0, 0);
  12.133 +
  12.134 +        // no RSet statistics output
  12.135 +        result = runTest(new String[] { "-XX:G1SummarizeRSetStatsPeriod=1" }, 3);
  12.136 +        expectStatistics(result, 0, 0);
  12.137 +
  12.138 +        // single RSet statistics output at the end
  12.139 +        result = runTest(new String[] { "-XX:+G1SummarizeRSetStats" }, 0);
  12.140 +        expectStatistics(result, 1, 0);
  12.141 +
  12.142 +        // single RSet statistics output at the end
  12.143 +        result = runTest(new String[] { "-XX:+G1SummarizeRSetStats" }, 2);
  12.144 +        expectStatistics(result, 1, 0);
  12.145 +
  12.146 +        // single RSet statistics output
  12.147 +        result = runTest(new String[] { "-XX:+G1SummarizeRSetStats", "-XX:G1SummarizeRSetStatsPeriod=1" }, 0);
  12.148 +        expectStatistics(result, 1, 0);
  12.149 +
  12.150 +        // two times RSet statistics output
  12.151 +        result = runTest(new String[] { "-XX:+G1SummarizeRSetStats", "-XX:G1SummarizeRSetStatsPeriod=1" }, 1);
  12.152 +        expectStatistics(result, 1, 1);
  12.153 +
  12.154 +        // four times RSet statistics output
  12.155 +        result = runTest(new String[] { "-XX:+G1SummarizeRSetStats", "-XX:G1SummarizeRSetStatsPeriod=1" }, 3);
  12.156 +        expectStatistics(result, 1, 3);
  12.157 +
  12.158 +        // three times RSet statistics output
  12.159 +        result = runTest(new String[] { "-XX:+G1SummarizeRSetStats", "-XX:G1SummarizeRSetStatsPeriod=2" }, 3);
  12.160 +        expectStatistics(result, 1, 2);
  12.161 +
  12.162 +        // single RSet statistics output
  12.163 +        result = runTest(new String[] { "-XX:+G1SummarizeRSetStats", "-XX:G1SummarizeRSetStatsPeriod=100" }, 3);
  12.164 +        expectStatistics(result, 1, 1);
  12.165 +    }
  12.166 +}
  12.167 +

mercurial