Initial implementation of statistical history (JDK-8212618) stuefe-statistical-history
authorstuefe
Fri, 07 Sep 2018 07:52:35 +0200
branchstuefe-statistical-history
changeset 57221 9653470b7294
parent 53949 4a99d3a6a86d
child 57244 a535e674d50d
Initial implementation of statistical history (JDK-8212618)
src/hotspot/os/aix/stathist_aix.cpp
src/hotspot/os/bsd/stathist_bsd.cpp
src/hotspot/os/linux/stathist_linux.cpp
src/hotspot/os/solaris/stathist_solaris.cpp
src/hotspot/os/windows/stathist_windows.cpp
src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp
src/hotspot/share/runtime/globals.hpp
src/hotspot/share/runtime/thread.cpp
src/hotspot/share/services/diagnosticCommand.cpp
src/hotspot/share/services/stathist.cpp
src/hotspot/share/services/stathist.hpp
src/hotspot/share/services/stathistDCmd.cpp
src/hotspot/share/services/stathistDCmd.hpp
src/hotspot/share/services/stathist_internals.hpp
src/hotspot/share/utilities/vmError.cpp
test/hotspot/jtreg/serviceability/dcmd/vm/StatHistTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/os/aix/stathist_aix.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+
+#include "runtime/os.hpp"
+#include "services/stathist_internals.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+namespace StatisticsHistory {
+
+bool platform_columns_initialize() {
+  return true;
+}
+
+void sample_platform_values(record_t* record) {
+}
+
+} // namespace StatisticsHistory
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/os/bsd/stathist_bsd.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+
+#include "runtime/os.hpp"
+#include "services/stathist_internals.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+namespace StatisticsHistory {
+
+bool platform_columns_initialize() {
+  return true;
+}
+
+void sample_platform_values(record_t* record) {
+}
+
+} // namespace StatisticsHistory
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/os/linux/stathist_linux.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "runtime/os.hpp"
+#include "services/stathist_internals.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+
+namespace StatisticsHistory {
+
+class ProcFile {
+  char* _buf;
+
+  // To keep the code simple, I just use a fixed sized buffer.
+  enum { bufsize = 4*K };
+
+public:
+
+  ProcFile() : _buf(NULL) {
+    _buf = (char*)os::malloc(bufsize, mtInternal);
+  }
+
+  ~ProcFile () {
+    os::free(_buf);
+  }
+
+  bool read(const char* filename) {
+
+    FILE* f = ::fopen(filename, "r");
+    if (f == NULL) {
+      return false;
+    }
+
+    size_t bytes_read = ::fread(_buf, 1, bufsize, f);
+    _buf[bufsize - 1] = '\0';
+
+    ::fclose(f);
+
+    return bytes_read > 0 && bytes_read < bufsize;
+  }
+
+  const char* text() const { return _buf; }
+
+  const char* get_prefixed_line(const char* prefix) const {
+    const char* p = ::strstr(_buf, prefix);
+    if (p != NULL) {
+      return p;
+    }
+    return NULL;
+  }
+
+  value_t parsed_prefixed_value(const char* prefix, size_t scale = 1) const {
+    value_t value = INVALID_VALUE;
+    const char* const s = get_prefixed_line(prefix);
+    if (s != NULL) {
+      errno = 0;
+      const char* p = s + ::strlen(prefix);
+      value = (value_t)::strtoll(p, NULL, 10);
+      if (value == 0 && errno != 0) {
+        value = INVALID_VALUE;
+      } else {
+        value *= scale;
+      }
+    }
+    return value;
+  }
+
+};
+
+struct cpu_values_t {
+  value_t user;
+  value_t nice;
+  value_t system;
+  value_t idle;
+  value_t iowait;
+  value_t steal;
+  value_t guest;
+  value_t guest_nice;
+};
+
+void parse_proc_stat_cpu_line(const char* line, cpu_values_t* out) {
+  // Note: existence of some of these values depends on kernel version
+  out->user = out->nice = out->system = out->idle = out->iowait = out->steal = out->guest = out->guest_nice =
+      INVALID_VALUE;
+  int user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
+  int num = ::sscanf(line,
+      "cpu %d %d %d %d %d %d %d %d %d %d",
+      &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice);
+  if (num >= 4) {
+    out->user = user;
+    out->nice = nice;
+    out->system = system;
+    out->idle = idle;
+    if (num >= 5) { // iowait (5) (since Linux 2.5.41)
+      out->iowait = iowait;
+      if (num >= 8) { // steal (8) (since Linux 2.6.11)
+        out->steal = steal;
+        if (num >= 9) { // guest (9) (since Linux 2.6.24)
+          out->guest = guest;
+          if (num >= 10) { // guest (9) (since Linux 2.6.33)
+            out->guest_nice = guest_nice;
+          }
+        }
+      }
+    }
+  }
+}
+
+
+/////// Columns ////////
+
+// A special class to display cpu time
+class CPUTimeColumn: public Column {
+
+  long _clk_tck;
+  int _num_cores;
+
+  int do_print(outputStream* st, value_t value, value_t last_value,
+      int last_value_age, const print_info_t* pi) const {
+    // CPU values may overflow, so the delta may be negative.
+    if (last_value > value) {
+      return 0;
+    }
+    int l = 0;
+    if (value != INVALID_VALUE && last_value != INVALID_VALUE) {
+
+      // If the last sample is less than one second old, we omit calculating the cpu
+      // usage.
+      if (last_value_age > 0) {
+
+        // Values are in ticks. Convert to ms.
+        const uint64_t value_ms = (value * 1000) / _clk_tck;
+        const uint64_t last_value_ms = (last_value * 1000) / _clk_tck;
+        const uint64_t delta_ms = value_ms - last_value_ms;
+
+        // Calculate the number of wallclock milliseconds for the delta interval...
+        const int age_ms = last_value_age * 1000;
+
+        // times number of available cores.
+        const int total_cpu_time_ms = age_ms * _num_cores;
+
+        // Put the spent cpu time in reference to the total available cpu time.
+        const float percentage = (100.0f * delta_ms) / total_cpu_time_ms;
+
+        char buf[32];
+        l = jio_snprintf(buf, sizeof(buf), "%.0f", percentage);
+        if (st != NULL) {
+          st->print_raw(buf);
+        }
+      }
+    }
+    return l;
+  }
+
+public:
+  CPUTimeColumn(const char* category, const char* header, const char* name, const char* description)
+    : Column(category, header, name, description)
+  {
+    _clk_tck = ::sysconf(_SC_CLK_TCK);
+    _num_cores = os::active_processor_count();
+  }
+
+};
+
+//static Column* g_col_system_memtotal = NULL;
+static Column* g_col_system_memfree = NULL;
+static Column* g_col_system_memavail = NULL;
+static Column* g_col_system_swap = NULL;
+
+static Column* g_col_system_pages_swapped_in = NULL;
+static Column* g_col_system_pages_swapped_out = NULL;
+
+static Column* g_col_system_num_procs_running = NULL;
+static Column* g_col_system_num_procs_blocked = NULL;
+
+static Column* g_col_system_cpu_user = NULL;
+static Column* g_col_system_cpu_system = NULL;
+static Column* g_col_system_cpu_idle = NULL;
+static Column* g_col_system_cpu_waiting = NULL;
+static Column* g_col_system_cpu_steal = NULL;
+static Column* g_col_system_cpu_guest = NULL;
+
+static Column* g_col_process_virt = NULL;
+static Column* g_col_process_rss = NULL;
+static Column* g_col_process_rssanon = NULL;
+static Column* g_col_process_rssfile = NULL;
+static Column* g_col_process_rssshmem = NULL;
+static Column* g_col_process_swapped_out = NULL;
+
+static Column* g_col_process_cpu_user = NULL;
+static Column* g_col_process_cpu_system = NULL;
+
+static Column* g_col_process_num_of = NULL;
+static Column* g_col_process_io_bytes_read = NULL;
+static Column* g_col_process_io_bytes_written = NULL;
+
+static Column* g_col_process_num_threads = NULL;
+
+bool platform_columns_initialize() {
+
+  // Order matters!
+//  g_col_system_memtotal = new MemorySizeColumn("system", NULL, "total", "Total physical memory.");
+
+  // Since free and avail are kind of redundant, only display free if avail is not available (very old kernels)
+  bool have_avail = false;
+  {
+    ProcFile bf;
+    if (bf.read("/proc/meminfo")) {
+      have_avail = (bf.parsed_prefixed_value("MemAvailable:", 1) != INVALID_VALUE);
+    }
+  }
+
+  if (have_avail) {
+    g_col_system_memavail = new MemorySizeColumn("system", NULL, "avail", "Memory available without swapping (>=3.14)");
+  } else {
+    g_col_system_memfree = new MemorySizeColumn("system", NULL, "free", "Unused memory");
+  }
+
+  g_col_system_swap = new MemorySizeColumn("system", NULL, "swap", "Swap space used");
+
+  g_col_system_pages_swapped_in = new DeltaValueColumn("system", NULL, "si", "Number of pages swapped in");
+
+  g_col_system_pages_swapped_out = new DeltaValueColumn("system", NULL, "so", "Number of pages pages swapped out");
+
+  g_col_system_num_procs_running = new PlainValueColumn("system", NULL, "pr", "Number of tasks running");
+  g_col_system_num_procs_blocked = new PlainValueColumn("system", NULL, "pb", "Number of tasks blocked");
+
+  g_col_system_cpu_user =     new CPUTimeColumn("system", "cpu", "us", "Global cpu user time");
+  g_col_system_cpu_system =   new CPUTimeColumn("system", "cpu", "sy", "Global cpu system time");
+  g_col_system_cpu_idle =     new CPUTimeColumn("system", "cpu", "id", "Global cpu idle time");
+  g_col_system_cpu_waiting =  new CPUTimeColumn("system", "cpu", "wa", "Global cpu time spent waiting for IO");
+  g_col_system_cpu_steal =    new CPUTimeColumn("system", "cpu", "st", "Global cpu time stolen");
+  g_col_system_cpu_guest =    new CPUTimeColumn("system", "cpu", "gu", "Global cpu time spent on guest");
+
+  g_col_process_virt = new MemorySizeColumn("process", NULL, "virt", "Virtual size");
+
+  bool have_rss_detail_info = false;
+  {
+    ProcFile bf;
+    if (bf.read("/proc/self/status")) {
+      have_rss_detail_info = bf.parsed_prefixed_value("RssAnon", 1) != INVALID_VALUE;
+    }
+  }
+  if (have_rss_detail_info) {
+    // Linux 4.5 ++
+    g_col_process_rss = new MemorySizeColumn("process", "rss", "all", "Resident set size, total");
+    g_col_process_rssanon = new MemorySizeColumn("process", "rss", "anon", "Resident set size, anonymous memory (>=4.5)");
+    g_col_process_rssfile = new MemorySizeColumn("process", "rss", "file", "Resident set size, file mappings (>=4.5)");
+    g_col_process_rssshmem = new MemorySizeColumn("process", "rss", "shm", "Resident set size, shared memory (>=4.5)");
+  } else {
+    g_col_process_rss = new MemorySizeColumn("process", NULL, "rss", "Resident set size, total");
+  }
+
+  g_col_process_swapped_out = new MemorySizeColumn("process", NULL, "swdo", "Memory swapped out");
+
+  g_col_process_cpu_user = new CPUTimeColumn("process", "cpu", "us", "Process cpu user time");
+
+  g_col_process_cpu_system = new CPUTimeColumn("process", "cpu", "sy", "Process cpu system time");
+
+  g_col_process_num_of = new PlainValueColumn("process", "io", "of", "Number of open files");
+
+  g_col_process_io_bytes_read = new DeltaMemorySizeColumn("process", "io", "rd", "IO bytes read from storage or cache");
+
+  g_col_process_io_bytes_written = new DeltaMemorySizeColumn("process", "io", "wr", "IO bytes written");
+
+  g_col_process_num_threads = new PlainValueColumn("process", NULL, "thr", "Number of native threads");
+
+
+  return true;
+}
+
+static void set_value_in_record(Column* col, record_t* record, value_t val) {
+  if (col != NULL) {
+    int index = col->index();
+    record->values[index] = val;
+  }
+}
+
+void sample_platform_values(record_t* record) {
+
+  int idx = 0;
+  value_t v = 0;
+
+  ProcFile bf;
+  if (bf.read("/proc/meminfo")) {
+
+    // All values in /proc/meminfo are in KB
+    const size_t scale = K;
+
+    set_value_in_record(g_col_system_memfree, record,
+        bf.parsed_prefixed_value("MemFree:", scale));
+
+    set_value_in_record(g_col_system_memavail, record,
+        bf.parsed_prefixed_value("MemAvailable:", scale));
+
+    value_t swap_total = bf.parsed_prefixed_value("SwapTotal:", scale);
+    value_t swap_free = bf.parsed_prefixed_value("SwapFree:", scale);
+    if (swap_total != INVALID_VALUE && swap_free != INVALID_VALUE) {
+      set_value_in_record(g_col_system_swap, record, swap_total - swap_free);
+    }
+
+  }
+
+  if (bf.read("/proc/vmstat")) {
+    set_value_in_record(g_col_system_pages_swapped_in, record, bf.parsed_prefixed_value("pswpin"));
+    set_value_in_record(g_col_system_pages_swapped_out, record, bf.parsed_prefixed_value("pswpout"));
+  }
+
+  if (bf.read("/proc/stat")) {
+    // Read and parse global cpu values
+    cpu_values_t values;
+    const char* line = bf.get_prefixed_line("cpu");
+    parse_proc_stat_cpu_line(line, &values);
+
+    set_value_in_record(g_col_system_cpu_user, record, values.user + values.nice);
+    set_value_in_record(g_col_system_cpu_system, record, values.system);
+    set_value_in_record(g_col_system_cpu_idle, record, values.idle);
+    set_value_in_record(g_col_system_cpu_waiting, record, values.iowait);
+    set_value_in_record(g_col_system_cpu_steal, record, values.steal);
+    set_value_in_record(g_col_system_cpu_guest, record, values.guest + values.guest_nice);
+
+    set_value_in_record(g_col_system_num_procs_running, record,
+        bf.parsed_prefixed_value("procs_running"));
+    set_value_in_record(g_col_system_num_procs_blocked, record,
+        bf.parsed_prefixed_value("procs_blocked"));
+  }
+
+  if (bf.read("/proc/self/status")) {
+
+    set_value_in_record(g_col_process_virt, record, bf.parsed_prefixed_value("VmSize:", K));
+    set_value_in_record(g_col_process_swapped_out, record, bf.parsed_prefixed_value("VmSwap:", K));
+    set_value_in_record(g_col_process_rss, record, bf.parsed_prefixed_value("VmRSS:", K));
+
+    set_value_in_record(g_col_process_rssanon, record, bf.parsed_prefixed_value("RssAnon:", K));
+    set_value_in_record(g_col_process_rssfile, record, bf.parsed_prefixed_value("RssFile:", K));
+    set_value_in_record(g_col_process_rssshmem, record, bf.parsed_prefixed_value("RssShmem:", K));
+
+    set_value_in_record(g_col_process_num_threads, record,
+        bf.parsed_prefixed_value("Threads:"));
+
+  }
+
+  // Number of open files: iterate over /proc/self/fd and count.
+  {
+    DIR* d = ::opendir("/proc/self/fd");
+    if (d != NULL) {
+      value_t v = 0;
+      struct dirent* en = NULL;
+      do {
+        en = ::readdir(d);
+        if (en != NULL) {
+          if (::strcmp(".", en->d_name) == 0 || ::strcmp("..", en->d_name) == 0 ||
+              ::strcmp("0", en->d_name) == 0 || ::strcmp("1", en->d_name) == 0 || ::strcmp("2", en->d_name) == 0) {
+            // omit
+          } else {
+            v ++;
+          }
+        }
+      } while(en != NULL);
+      ::closedir(d);
+      set_value_in_record(g_col_process_num_of, record, v);
+    }
+  }
+
+  if (bf.read("/proc/self/io")) {
+    set_value_in_record(g_col_process_io_bytes_read, record,
+        bf.parsed_prefixed_value("rchar:"));
+    set_value_in_record(g_col_process_io_bytes_written, record,
+        bf.parsed_prefixed_value("wchar:"));
+  }
+
+  if (bf.read("/proc/self/stat")) {
+    const char* text = bf.text();
+    // See man proc(5)
+    // (14) utime  %lu
+    // (15) stime  %lu
+
+    long unsigned cpu_utime = 0;
+    long unsigned cpu_stime = 0;
+    ::sscanf(text, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &cpu_utime, &cpu_stime);
+    set_value_in_record(g_col_process_cpu_user, record, cpu_utime);
+    set_value_in_record(g_col_process_cpu_system, record, cpu_stime);
+  }
+
+}
+
+} // namespace StatisticsHistory
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/os/solaris/stathist_solaris.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+
+#include "runtime/os.hpp"
+#include "services/stathist_internals.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+
+namespace StatisticsHistory {
+
+bool platform_columns_initialize() {
+  return true;
+}
+
+void sample_platform_values(record_t* record) {
+}
+
+} // namespace StatisticsHistory
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/os/windows/stathist_windows.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+
+#include "runtime/os.hpp"
+#include "services/stathist_internals.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+#include <psapi.h>
+
+
+namespace StatisticsHistory {
+
+static Column* g_col_system_memoryload = NULL;
+static Column* g_col_system_avail_phys = NULL;
+static Column* g_col_process_working_set_size = NULL;
+static Column* g_col_process_commit_charge = NULL;
+
+bool platform_columns_initialize() {
+  g_col_system_memoryload = new PlainValueColumn("system", NULL, "mload",
+      "Approximate percentage of physical memory that is in use.");
+
+  // MEMORYSTATUSEX ullAvailPhys
+  g_col_system_avail_phys = new MemorySizeColumn("system", NULL, "avail-phys",
+      "Amount of physical memory currently available.");
+
+  // PROCESS_MEMORY_COUNTERS_EX WorkingSetSize
+  g_col_process_working_set_size = new MemorySizeColumn("process", NULL, "wset",
+      "Working set size");
+
+  // PROCESS_MEMORY_COUNTERS_EX PrivateUsage
+  g_col_process_commit_charge = new MemorySizeColumn("process", NULL, "comch",
+      "Commit charge");
+
+
+  return true;
+}
+
+static void set_value_in_record(Column* col, record_t* record, value_t val) {
+  if (col != NULL) {
+    int index = col->index();
+    record->values[index] = val;
+  }
+}
+
+void sample_platform_values(record_t* record) {
+
+  MEMORYSTATUSEX mse;
+  mse.dwLength = sizeof(mse);
+  if (::GlobalMemoryStatusEx(&mse)) {
+    set_value_in_record(g_col_system_memoryload, record, mse.dwMemoryLoad);
+    set_value_in_record(g_col_system_avail_phys, record, mse.ullAvailPhys);
+  }
+
+  PROCESS_MEMORY_COUNTERS cnt;
+  cnt.cb = sizeof(cnt);
+  if (::GetProcessMemoryInfo(::GetCurrentProcess(), &cnt, sizeof(cnt))) {
+    set_value_in_record(g_col_process_working_set_size, record, cnt.WorkingSetSize);
+    set_value_in_record(g_col_process_commit_charge, record, cnt.PagefileUsage);
+  }
+
+}
+
+} // namespace StatisticsHistory
--- a/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp	Thu Feb 28 13:37:03 2019 +0800
+++ b/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp	Fri Sep 07 07:52:35 2018 +0200
@@ -28,7 +28,11 @@
 #include "classfile/classLoaderDataGraph.hpp"
 #include "classfile/javaClasses.hpp"
 #include "oops/oop.inline.hpp"
+// SapMachine 2019-02-20 : stathist
+#include "runtime/globals.hpp"
 #include "runtime/atomic.hpp"
+// SapMachine 2019-02-20 : stathist
+#include "services/stathist.hpp"
 
 inline ClassLoaderData *ClassLoaderDataGraph::find_or_create(Handle loader) {
   guarantee(loader() != NULL && oopDesc::is_oop(loader()), "Loader must be oop");
@@ -51,20 +55,36 @@
 
 void ClassLoaderDataGraph::inc_instance_classes(size_t count) {
   Atomic::add(count, &_num_instance_classes);
+  // SapMachine 2019-02-20 : stathist
+  if (EnableStatHist) {
+    StatisticsHistory::counters::inc_classes_loaded(count);
+  }
 }
 
 void ClassLoaderDataGraph::dec_instance_classes(size_t count) {
   assert(count <= _num_instance_classes, "Sanity");
   Atomic::sub(count, &_num_instance_classes);
+  // SapMachine 2019-02-20 : stathist
+  if (EnableStatHist) {
+    StatisticsHistory::counters::inc_classes_unloaded(count);
+  }
 }
 
 void ClassLoaderDataGraph::inc_array_classes(size_t count) {
   Atomic::add(count, &_num_array_classes);
+  // SapMachine 2019-02-20 : stathist
+  if (EnableStatHist) {
+    StatisticsHistory::counters::inc_classes_loaded(count);
+  }
 }
 
 void ClassLoaderDataGraph::dec_array_classes(size_t count) {
   assert(count <= _num_array_classes, "Sanity");
   Atomic::sub(count, &_num_array_classes);
+  // SapMachine 2019-02-20 : stathist
+  if (EnableStatHist) {
+    StatisticsHistory::counters::inc_classes_unloaded(count);
+  }
 }
 
 bool ClassLoaderDataGraph::should_clean_metaspaces_and_reset() {
--- a/src/hotspot/share/runtime/globals.hpp	Thu Feb 28 13:37:03 2019 +0800
+++ b/src/hotspot/share/runtime/globals.hpp	Fri Sep 07 07:52:35 2018 +0200
@@ -604,6 +604,16 @@
   develop(bool, Verbose, false,                                             \
           "Print additional debugging information from other modes")        \
                                                                             \
+  /* SapMachine 2019-02-20 : stathist */                                    \
+  product(bool, EnableStatHist, true,                                       \
+          "Enable Statistics history")                                      \
+                                                                            \
+  product(uintx, StatHistSampleInterval, 0,                                 \
+          "Statistics history sample rate interval (0=default)")            \
+                                                                            \
+  experimental(bool, StatHistLockFree, false,                               \
+          "Do not lock when sampling")                                      \
+                                                                            \
   develop(bool, PrintMiscellaneous, false,                                  \
           "Print uncategorized debugging information (requires +Verbose)")  \
                                                                             \
--- a/src/hotspot/share/runtime/thread.cpp	Thu Feb 28 13:37:03 2019 +0800
+++ b/src/hotspot/share/runtime/thread.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -106,6 +106,8 @@
 #include "services/attachListener.hpp"
 #include "services/management.hpp"
 #include "services/memTracker.hpp"
+// SapMachine 2019-02-20 : stathist
+#include "services/stathist.hpp"
 #include "services/threadService.hpp"
 #include "utilities/align.hpp"
 #include "utilities/copy.hpp"
@@ -4001,6 +4003,11 @@
   StatSampler::engage();
   if (CheckJNICalls)                  JniPeriodicChecker::engage();
 
+  // SapMachine 2019-02-20 : stathist
+  if (EnableStatHist) {
+    StatisticsHistory::initialize();
+  }
+
   BiasedLocking::init();
 
 #if INCLUDE_RTM_OPT
@@ -4433,6 +4440,10 @@
   p->set_on_thread_list();
 
   _number_of_threads++;
+
+  // SapMachine 2019-02-20 : stathist
+  StatisticsHistory::counters::inc_threads_created(1);
+
   oop threadObj = p->threadObj();
   bool daemon = true;
   // Bootstrapping problem: threadObj can be null for initial
--- a/src/hotspot/share/services/diagnosticCommand.cpp	Thu Feb 28 13:37:03 2019 +0800
+++ b/src/hotspot/share/services/diagnosticCommand.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -45,6 +45,8 @@
 #include "services/diagnosticFramework.hpp"
 #include "services/heapDumper.hpp"
 #include "services/management.hpp"
+// SapMachine 2019-02-20 : stathist
+#include "services/stathistDCmd.hpp"
 #include "services/writeableFlags.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/formatBuffer.hpp"
@@ -85,6 +87,8 @@
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<RunFinalizationDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<HeapInfoDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<FinalizerInfoDCmd>(full_export, true, false));
+  // SapMachine 2019-02-20 : stathist
+  DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<StatisticsHistory::StatHistDCmd>(full_export, true, false));
 #if INCLUDE_SERVICES
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<HeapDumpDCmd>(DCmd_Source_Internal | DCmd_Source_AttachAPI, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassHistogramDCmd>(full_export, true, false));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/services/stathist.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,1193 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+
+
+#include "gc/shared/collectedHeap.hpp"
+#include "classfile/classLoaderDataGraph.inline.hpp"
+#include "code/codeCache.hpp"
+#include "memory/allocation.hpp"
+#include "memory/universe.hpp"
+#include "runtime/thread.hpp"
+#include "services/memTracker.hpp"
+#include "services/stathist.hpp"
+#include "services/stathist_internals.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/ostream.hpp"
+
+
+#include <locale.h>
+#include <time.h>
+
+
+// Define this switch to limit sampling to those values which can be obtained without locking.
+#undef NEVER_LOCK_WHEN_SAMPLING
+
+namespace StatisticsHistory {
+
+namespace counters {
+
+// These are counters for the statistics history. Ideally, they would live
+// inside their thematical homes, e.g. thread.cpp or classLoaderDataGraph.cpp,
+// however since this is unlikely ever to be brought upstream we keep them separate
+// from central coding to ease maintenance.
+static volatile size_t g_classes_loaded = 0;
+static volatile size_t g_classes_unloaded = 0;
+static volatile size_t g_threads_created = 0;
+
+void inc_classes_loaded(size_t count) {
+  Atomic::add(count, &g_classes_loaded);
+}
+
+void inc_classes_unloaded(size_t count) {
+  Atomic::add(count, &g_classes_unloaded);
+}
+
+void inc_threads_created(size_t count) {
+  Atomic::add(count, &g_threads_created);
+}
+
+} // namespace counters
+
+// helper function for the missing outputStream::put(int c, int repeat)
+static void ostream_put_n(outputStream* st, int c, int repeat) {
+  for (int i = 0; i < repeat; i ++) {
+    st->put(c);
+  }
+}
+
+static size_t record_size_in_bytes() {
+  const int num_columns = ColumnList::the_list()->num_columns();
+  return sizeof(record_t) + sizeof(value_t) * (num_columns - 1);
+}
+
+static void print_text_with_dashes(outputStream* st, const char* text, int width) {
+  assert(width > 0, "Sanity");
+  // Print the name centered within the width like this
+  // ----- system ------
+  int extra_space = width - (int)strlen(text);
+  if (extra_space > 0) {
+    int left_space = extra_space / 2;
+    int right_space = extra_space - left_space;
+    ostream_put_n(st, '-', left_space);
+    st->print_raw(text);
+    ostream_put_n(st, '-', right_space);
+  } else {
+    ostream_put_n(st, '-', width);
+  }
+}
+
+// Helper function for printing:
+// Print to ostream, but only if ostream is given. In any case return number ofcat_process = 10,
+// characters printed (or which would have been printed).
+static
+ATTRIBUTE_PRINTF(2, 3)
+int printf_helper(outputStream* st, const char *fmt, ...) {
+  // We only print numbers, so a small buffer is fine.
+  char buf[64];
+  va_list args;
+  int len = 0;
+  va_start(args, fmt);
+  len = jio_vsnprintf(buf, sizeof(buf), fmt, args);
+  va_end(args);
+  assert((size_t)len < sizeof(buf), "Truncation. Increase bufsize.");
+  if (st != NULL) {
+    st->print_raw(buf);
+  }
+  return len;
+}
+
+// length of time stamp
+#define TIMESTAMP_LEN 19
+// number of spaces after time stamp
+#define TIMESTAMP_DIVIDER_LEN 3
+static void print_timestamp(outputStream* st, time_t t) {
+  struct tm _tm;
+  if (os::localtime_pd(&t, &_tm) == &_tm) {
+    char buf[32];
+    ::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &_tm);
+    st->print("%*s", TIMESTAMP_LEN, buf);
+  }
+}
+
+// A little RAII class to temporarily change locale to "C"
+class CLocaleMark {
+  const char* _orig;
+public:
+  CLocaleMark() {
+    _orig = ::setlocale(LC_NUMERIC, NULL);
+    ::setlocale(LC_NUMERIC, "C");
+  }
+  ~CLocaleMark() {
+    ::setlocale(LC_NUMERIC, _orig);
+  }
+};
+
+////// class ColumnList methods ////
+
+ColumnList* ColumnList::_the_list = NULL;
+
+bool ColumnList::initialize() {
+  _the_list = new ColumnList();
+  return _the_list != NULL;
+}
+
+void ColumnList::add_column(Column* c) {
+  assert(c->index() == -1, "Do not add twice.");
+  Column* c_last = _last;
+  if (_last != NULL) {
+    _last->_next = c;
+    _last = c;
+  } else {
+    _first = _last = c;
+  }
+  // fix indices (describe position of column within table/category/header
+  c->_idx = c->_idx_cat = c->_idx_hdr = 0;
+  if (c_last != NULL) {
+    c->_idx = c_last->_idx + 1;
+    if (::strcmp(c->category(), c_last->category()) == 0) { // same category as last column?
+      c->_idx_cat = c_last->_idx_cat + 1;
+    }
+    if (c->header() != NULL && c_last->header() != NULL &&
+        ::strcmp(c_last->header(), c->header()) == 0) { // have header and same as last column?
+      c->_idx_hdr = c_last->_idx_hdr + 1;
+    }
+  }
+  _num_columns ++;
+}
+
+////////////////////
+
+// At various places we need a scratch buffer of ints with one element per column; since
+// after initialization the number of columns is fixed, we pre-create this.
+static int* g_widths = NULL;
+
+bool initialize_widths_buffer() {
+  assert(ColumnList::the_list() != NULL && g_widths == NULL, "Initialization order problem.");
+  g_widths = (int*)os::malloc(sizeof(int) * ColumnList::the_list()->num_columns(), mtInternal);
+  return g_widths != NULL;
+}
+
+// pre-allocated space for a single record used to take current values when printing via dcmd.
+static record_t* g_record_now = NULL;
+
+bool initialize_space_for_now_record() {
+  assert(ColumnList::the_list() != NULL && g_record_now == NULL, "Initialization order problem.");
+  g_record_now = (record_t*) os::malloc(record_size_in_bytes(), mtInternal);
+  return g_record_now != NULL;
+}
+
+////////////////////
+
+static void print_category_line(outputStream* st, int widths[], const print_info_t* pi) {
+
+  assert(pi->cvs == false, "Not in cvs mode");
+  ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
+
+  const Column* c = ColumnList::the_list()->first();
+  assert(c != NULL, "no columns?");
+  const char* last_category_text = NULL;
+  int width = 0;
+
+  while(c != NULL) {
+    if (c->index_within_category_section() == 0) {
+      if (width > 0) {
+        // Print category label centered over the last n columns, surrounded by dashes.
+        print_text_with_dashes(st, last_category_text, width - 1);
+        st->put(' ');
+      }
+      width = 0;
+    }
+    width += widths[c->index()];
+    width += 1; // divider between columns
+    last_category_text = c->category();
+    c = c->next();
+  }
+  print_text_with_dashes(st, last_category_text, width - 1);
+  st->cr();
+}
+
+static void print_header_line(outputStream* st, int widths[], const print_info_t* pi) {
+
+  assert(pi->cvs == false, "Not in cvs mode");
+  ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
+
+  const Column* c = ColumnList::the_list()->first();
+  assert(c != NULL, "no columns?");
+  const char* last_header_text = NULL;
+  int width = 0;
+
+  while(c != NULL) {
+    if (c->index_within_header_section() == 0) { // First in header section
+      if (width > 0) {
+        if (last_header_text != NULL) {
+          // Print header label centered over the last n columns, surrounded by dashes.
+          print_text_with_dashes(st, last_header_text, width - 1);
+          st->put(' '); // divider
+        } else {
+          // the last n columns had no header. Just fill with blanks.
+          ostream_put_n(st, ' ', width);
+        }
+      }
+      width = 0;
+    }
+    width += widths[c->index()];
+    width += 1; // divider between columns
+    last_header_text = c->header();
+    c = c->next();
+  }
+  if (width > 0 && last_header_text != NULL) {
+    print_text_with_dashes(st, last_header_text, width - 1);
+  }
+  st->cr();
+}
+
+static void print_column_names(outputStream* st, int widths[], const print_info_t* pi) {
+
+  // Leave space for timestamp column
+  if (pi->cvs == false) {
+    ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
+  } else {
+    st->put(',');
+  }
+
+  const Column* c = ColumnList::the_list()->first();
+  const Column* previous = NULL;
+  while (c != NULL) {
+    if (pi->cvs == false) {
+      st->print("%-*s ", widths[c->index()], c->name());
+    } else { // cvs mode
+      // cvs: use comma as delimiter, don't pad, and precede name with header if there is one.
+      if (c->header() != NULL) {
+        st->print("%s-", c->header());
+      }
+      st->print("%s,", c->name());
+    }
+    previous = c;
+    c = c->next();
+  }
+  st->cr();
+}
+
+static void print_legend(outputStream* st, const print_info_t* pi) {
+  const Column* c = ColumnList::the_list()->first();
+  const Column* c_prev = NULL;
+  while (c != NULL) {
+    // Print category label.
+    if (c->index_within_category_section() == 0) {
+      print_text_with_dashes(st, c->category(), 30);
+      st->cr();
+    }
+    // print column name and description
+    const int min_width_column_label = 16;
+    char buf[32];
+    if (c->header() != NULL) {
+      jio_snprintf(buf, sizeof(buf), "%s-%s", c->header(), c->name());
+    } else {
+      jio_snprintf(buf, sizeof(buf), "%s", c->name());
+    }
+    st->print("%*s: %s", min_width_column_label, buf, c->description());
+
+    // If memory units are not dynamic (otion scale), print out the unit as well.
+    if (c->is_memory_size() && pi->scale != 0) {
+      st->print_raw(" [mem]");
+    }
+
+    // If column is a delta value, indicate so
+    if (c->is_delta()) {
+      st->print_raw(" [delta]");
+    }
+
+    st->cr();
+
+    c_prev = c;
+    c = c->next();
+  }
+  st->cr();
+  st->print_cr("[delta] values refer to the previous measurement.");
+  if (pi->scale != 0) {
+    const char* display_unit = NULL;
+    switch (pi->scale) {
+      case K: display_unit = "KB"; break;
+      case M: display_unit = "MB"; break;
+      case G: display_unit = "GB"; break;
+      default: ShouldNotReachHere();
+    }
+    st->print_cr("[mem] values are in %s.", display_unit);
+  }
+}
+
+// Print a human readable size.
+// byte_size: size, in bytes, to be printed.
+// scale: K,M,G or 0 (dynamic)
+// width: printing width.
+static int print_memory_size(outputStream* st, size_t byte_size, size_t scale)  {
+
+  bool print_unit = false;
+
+  if (scale == 0) {
+    print_unit = true;
+    // Dynamic mode. Choose scale for this value.
+    if (byte_size == 0) {
+      scale = K;
+    } else {
+      if (byte_size >= G) {
+        scale = G;
+      } else if (byte_size >= M) {
+        scale = M;
+      } else {
+        scale = K;
+      }
+    }
+  }
+
+  const char* display_unit = "";
+  if (print_unit) {
+    switch(scale) {
+      case K: display_unit = "k"; break;
+      case M: display_unit = "m"; break;
+      case G: display_unit = "g"; break;
+      default:
+        ShouldNotReachHere();
+    }
+  }
+
+  int l = 0;
+  float display_value = (float) byte_size / scale;
+  // Values smaller than 1M are shown are rounded up to whole numbers to de-clutter
+  // the display. Who cares for half kbytes.
+  int precision = scale < G ? 0 : 1;
+
+  if (byte_size > 0 && byte_size < K) {
+    // Prevent values smaller than one K but not 0 showing up as .
+    l = printf_helper(st, "<1%s", display_unit);
+  } else {
+    l = printf_helper(st, "%.*f%s", precision, display_value, display_unit);
+  }
+  return l;
+
+}
+
+///////// class Column and childs ///////////
+
+Column::Column(const char* category, const char* header, const char* name, const char* description)
+  : _category(category),
+    _header(header), // may be NULL
+    _name(name),
+    _description(description),
+    _next(NULL), _idx(-1),
+    _idx_cat(-1), _idx_hdr(-1)
+{
+  ColumnList::the_list()->add_column(this);
+}
+
+void Column::print_value(outputStream* st, value_t value, value_t last_value,
+    int last_value_age, int min_width, const print_info_t* pi) const {
+#ifdef ASSERT
+  if (pi->raw) {
+    printf_helper(st, UINT64_FORMAT, value);
+    return;
+  }
+#endif
+  // We print all values right aligned.
+  int needed = calc_print_size(value, last_value, last_value_age, pi);
+  if (pi->cvs == false && min_width > needed) {
+    // In ascii (non cvs) mode, pad to minimum width
+    ostream_put_n(st, ' ', min_width - needed);
+  }
+  do_print(st, value, last_value, last_value_age, pi);
+}
+
+// Returns the number of characters this value needs to be printed.
+int Column::calc_print_size(value_t value, value_t last_value,
+    int last_value_age, const print_info_t* pi) const {
+  return do_print(NULL, value, last_value, last_value_age, pi);
+}
+
+int PlainValueColumn::do_print(outputStream* st, value_t value,
+    value_t last_value, int last_value_age, const print_info_t* pi) const
+{
+  int l = 0;
+  if (value != INVALID_VALUE) {
+    l = printf_helper(st, UINT64_FORMAT, value);
+  }
+  return l;
+}
+
+int DeltaValueColumn::do_print(outputStream* st, value_t value,
+    value_t last_value, int last_value_age, const print_info_t* pi) const {
+  if (_show_only_positive && last_value > value) {
+    // we assume the underlying value to be monotonically raising, and that
+    // any negative delta would be just a fluke (e.g. counter overflows)
+    // we do not want to show
+    return 0;
+  }
+  int l = 0;
+  if (value != INVALID_VALUE && last_value != INVALID_VALUE) {
+    l = printf_helper(st, INT64_FORMAT, (int64_t)(value - last_value));
+  }
+  return l;
+}
+
+int MemorySizeColumn::do_print(outputStream* st, value_t value,
+    value_t last_value, int last_value_age, const print_info_t* pi) const {
+  int l = 0;
+  if (value != INVALID_VALUE) {
+    l = print_memory_size(st, value, pi->scale);
+  }
+  return l;
+}
+
+int DeltaMemorySizeColumn::do_print(outputStream* st, value_t value,
+    value_t last_value, int last_value_age, const print_info_t* pi) const {
+  int l = 0;
+  if (value != INVALID_VALUE && last_value != INVALID_VALUE) {
+    l = print_memory_size(st, value - last_value, pi->scale);
+  }
+  return l;
+}
+
+////////////// Record printing ///////////////////////////
+
+// Print one record.
+static void print_one_record(outputStream* st, const record_t* record,
+    const record_t* last_record, const int widths[], const print_info_t* pi) {
+
+  // Print timestamp and divider
+  if (record->timestamp == 0) {
+    st->print("%*s", TIMESTAMP_LEN, "Now");
+  } else {
+    print_timestamp(st, record->timestamp);
+  }
+
+  if (pi->cvs == false) {
+    ostream_put_n(st, ' ', TIMESTAMP_DIVIDER_LEN);
+  } else {
+    st->put(',');
+  }
+
+  const Column* c = ColumnList::the_list()->first();
+  while (c != NULL) {
+    const int idx = c->index();
+    const value_t v = record->values[idx];
+    value_t v2 = INVALID_VALUE;
+    int age = -1;
+    if (last_record != NULL) {
+      v2 = last_record->values[idx];
+      age = record->timestamp - last_record->timestamp;
+    }
+    const int min_width = widths[idx];
+    c->print_value(st, v, v2, age, min_width, pi);
+    st->put(pi->cvs ? ',' : ' ');
+    c = c->next();
+  }
+  st->cr();
+}
+
+// For each value in record, update the width in the widths array if it is smaller than
+// the value printing width
+static void update_widths_from_one_record(const record_t* record, const record_t* last_record, int widths[],
+    const print_info_t* pi) {
+  const Column* c = ColumnList::the_list()->first();
+  while (c != NULL) {
+    const int idx = c->index();
+    const value_t v = record->values[idx];
+    value_t v2 = INVALID_VALUE;
+    int age = -1;
+    if (last_record != NULL) {
+      v2 = last_record->values[idx];
+      age = record->timestamp - last_record->timestamp;
+    }
+    int needed = c->calc_print_size(v, v2, age, pi);
+    if (widths[idx] < needed) {
+      widths[idx] = needed;
+    }
+    c = c->next();
+  }
+}
+
+////////////// Class RecordTable /////////////////////////
+
+class RecordTable : public CHeapObj<mtInternal> {
+
+  const int _num_records;
+
+  record_t* _records;
+
+  int _pos;
+  bool _did_wrap;
+
+  RecordTable* const _follower;
+  const int _follower_ratio;
+  int _follower_countdown;
+
+  void check_pos(int pos) const {
+    assert(pos >= 0 && pos < _num_records, "invalid position");
+  }
+
+  int preceeding_pos(int pos) const {
+    check_pos(pos);
+    int p2 = pos - 1;
+    if (p2 == -1) {
+      if (_did_wrap) {
+        p2 = _num_records - 1;
+      }
+    } else if (p2 == _pos) {
+      assert(_did_wrap, "Sanity");
+      p2 = -1;
+    }
+    return p2;
+  }
+
+  record_t* at(int pos) const {
+    check_pos(pos);
+    return (record_t*) ((address)_records + (record_size_in_bytes() * pos));
+  }
+
+  // May return NULL
+  const record_t* preceeding(int pos) const {
+    int p2 = preceeding_pos(pos);
+    if (p2 != -1) {
+      return at(p2);
+    }
+    return NULL;
+  }
+
+  class ConstReverseIterator {
+    const RecordTable* const _rt;
+    int _p;
+
+  public:
+
+    ConstReverseIterator(const RecordTable* rt) : _rt(rt), _p(-1) {
+      _p = _rt->preceeding_pos(_rt->_pos);
+    }
+
+    bool valid() const { return _p != -1; }
+
+    void step() {
+      if (valid()) {
+        _p = _rt->preceeding_pos(_p);
+      }
+    }
+
+    const record_t* get() const {
+      assert(valid(), "Sanity");
+      return _rt->at(_p);
+    }
+
+    // May return NULL
+    const record_t* get_preceeding() const {
+      return _rt->preceeding(_p);
+    }
+
+  }; // end ConstReverseIterator
+
+
+  void add_record(const record_t* record) {
+    ::memcpy(current_record(), record, record_size_in_bytes());
+    finish_current_record();
+  }
+
+  // This "dry-prints" all records just to calculate the maximum print width, per column, needed to
+  // display the values.
+  void update_widths_from_all_records(int widths[], const print_info_t* pi) const {
+
+    // reset widths - note: minimum width is the length of the name of the column.
+    const Column* c = ColumnList::the_list()->first();
+    while (c != NULL) {
+      widths[c->index()] = (int)::strlen(c->name());
+      c = c->next();
+    }
+
+    ConstReverseIterator it(this);
+    while(it.valid()) {
+      const record_t* record = it.get();
+      const record_t* previous_record = it.get_preceeding();
+      update_widths_from_one_record(record, previous_record, widths, pi);
+      it.step();
+    }
+  }
+
+  // Print all records.
+  void print_all_records(outputStream* st, const int widths[], const print_info_t* pi) const {
+    ConstReverseIterator it(this);
+    while(it.valid()) {
+      const record_t* record = it.get();
+      const record_t* previous_record = it.get_preceeding();
+      print_one_record(st, record, previous_record, widths, pi);
+      it.step();
+    }
+  }
+
+  bool is_empty() const {
+    return _pos == 0 && _did_wrap == false;
+  }
+
+public:
+
+  RecordTable(int num_records, RecordTable* follower = NULL, int follower_ratio = -1)
+    : _num_records(num_records),
+      _records(NULL), _pos(0), _did_wrap(false),
+      _follower(follower), _follower_ratio(follower_ratio),
+      _follower_countdown(0)
+  {}
+
+  bool initialize() {
+    _records = (record_t*) os::malloc(record_size_in_bytes() * _num_records, mtInternal);
+    return _records != NULL;
+  }
+
+  // returns the pointer to the current, unfinished record.
+  record_t* current_record() {
+    return at(_pos);
+  }
+
+  // finish the current record: advances the write position in the FIFO buffer by one.
+  // Should that cause a record to fall out of the FIFO end, it propagates the record to
+  // the follower table if needed.
+  void finish_current_record() {
+    _pos ++;
+    if (_pos == _num_records) {
+      _pos = 0;
+      _did_wrap = true;
+    }
+    if (_did_wrap) {
+      // propagate old record if needed.
+      if (_follower != NULL && _follower_countdown == 0) {
+        _follower->add_record(current_record());
+        _follower_countdown = _follower_ratio; // reset countdown.
+      }
+      _follower_countdown --; // count down.
+    }
+  }
+
+  void print_table(outputStream* st, const print_info_t* pi, const record_t* values_now = NULL) const {
+
+    // print numbers with C locale to make parsing easier.
+    CLocaleMark clm;
+
+    if (is_empty() && values_now == NULL) {
+      st->print_cr("(no records)");
+      return;
+    }
+
+    const record_t* const youngest_in_table = preceeding(_pos);
+
+    // Before actually printing, lets calculate the printing widths
+    // (if now-values are given, they are part of the table too, so include them in the widths calculation)
+    update_widths_from_all_records(g_widths, pi);
+    if (values_now != NULL) {
+      update_widths_from_one_record(values_now, youngest_in_table, g_widths, pi);
+    }
+
+    // Print headers (not in cvs mode)
+    if (pi->cvs == false) {
+      print_category_line(st, g_widths, pi);
+      print_header_line(st, g_widths, pi);
+    }
+    print_column_names(st, g_widths, pi);
+    st->cr();
+
+    // Now print the actual values. Youngest to oldest, first one the now-values.
+    if (values_now != NULL) {
+      print_one_record(st, values_now, youngest_in_table, g_widths, pi);
+    }
+    print_all_records(st, g_widths, pi);
+
+  }
+};
+
+class RecordTables: public CHeapObj<mtInternal> {
+
+  enum {
+    // short term: 15 seconds per sample, 60 samples or 15 minutes total
+    short_term_interval_default = 15,
+    short_term_num_samples = 60,
+
+    // mid term: 15 minutes per sample (aka 60 short term samples), 96 samples or 24 hours in total
+    mid_term_interval_ratio = 60,
+    mid_term_num_samples = 96,
+
+    // long term history: 2 hour intervals (aka 8 mid term samples), 120 samples or 10 days in total
+    long_term_interval_ratio = 8,
+    long_term_num_samples = 120
+  };
+
+  int _short_term_interval;
+
+  RecordTable* _short_term_table;
+  RecordTable* _mid_term_table;
+  RecordTable* _long_term_table;
+
+  static RecordTables* _the_tables;
+
+  // Call this after column list has been initialized.
+  bool initialize(int short_term_interval) {
+
+    // Calculate intervals
+    _short_term_interval = short_term_interval;
+
+    // Initialize tables, oldest first (since it has no follower)
+    _long_term_table = new RecordTable(long_term_num_samples);
+    if (_long_term_table == NULL || !_long_term_table->initialize()) {
+      return false;
+    }
+    _mid_term_table = new RecordTable(mid_term_num_samples,
+      _long_term_table, long_term_interval_ratio);
+    if (_mid_term_table == NULL || !_mid_term_table->initialize()) {
+      return false;
+    }
+    _short_term_table = new RecordTable(short_term_num_samples,
+        _mid_term_table, mid_term_interval_ratio);
+    if (_short_term_table == NULL || !_short_term_table->initialize()) {
+      return false;
+    }
+
+    return true;
+
+  }
+
+public:
+
+  static RecordTables* the_tables() { return _the_tables; }
+
+  // Call this after column list has been initialized.
+  static bool initialize() {
+
+    _the_tables = new RecordTables();
+    if (_the_tables == NULL) {
+      return false;
+    }
+
+    const int short_term_interval = StatHistSampleInterval != 0 ?
+        StatHistSampleInterval : short_term_interval_default;
+    return _the_tables->initialize(short_term_interval);
+
+  }
+
+  void print_all(outputStream* st, const print_info_t* pi, const record_t* values_now = NULL) const {
+
+    st->print_cr("Short Term Values:");
+    // At the start of the short term table we print the current (now) values. The intent is to be able
+    // to see very short term developments (e.g. a spike in heap usage in the last n seconds)
+    _short_term_table->print_table(st, pi, values_now);
+    st->cr();
+
+    st->print_cr("Mid Term Values:");
+    _mid_term_table->print_table(st, pi);
+    st->cr();
+
+    st->print_cr("Long Term Values:");
+    _long_term_table->print_table(st, pi);
+    st->cr();
+
+  }
+
+  RecordTable* first_table() const {
+    return _short_term_table;
+  }
+
+  int short_term_interval() const {
+    return _short_term_interval;
+  }
+
+};
+
+RecordTables* RecordTables::_the_tables = NULL;
+
+static void sample_values(record_t* record, bool avoid_locking) {
+
+  // reset all values to be invalid.
+  const ColumnList* clist = ColumnList::the_list();
+  for (int colno = 0; colno < clist->num_columns(); colno ++) {
+    record->values[colno] = INVALID_VALUE;
+  }
+
+  // sample...
+  sample_jvm_values(record, avoid_locking);
+  sample_platform_values(record);
+}
+
+class SamplerThread: public NamedThread {
+
+  bool _stop;
+
+  void take_sample() {
+
+    const ColumnList* clist = ColumnList::the_list();
+    RecordTable* record_table = RecordTables::the_tables()->first_table();
+    record_t* record = record_table->current_record();
+
+    ::time(&record->timestamp);
+
+    sample_values(record, StatHistLockFree);
+
+    // After sampling, finish record.
+    record_table->finish_current_record();
+
+  }
+
+public:
+
+  SamplerThread()
+    : NamedThread()
+    , _stop(false)
+  {
+    this->set_name("stathist sampler thread");
+  }
+
+  virtual void run() {
+    record_stack_base_and_size();
+    for (;;) {
+      take_sample();
+      os::sleep(this, RecordTables::the_tables()->short_term_interval() * 1000, false);
+      if (_stop) {
+        break;
+      }
+    }
+  }
+
+  void stop() {
+    _stop = true;
+  }
+
+};
+
+static SamplerThread* g_sampler_thread = NULL;
+
+static bool initialize_sampler_thread() {
+  g_sampler_thread = new SamplerThread();
+  if (g_sampler_thread != NULL) {
+    if (os::create_thread(g_sampler_thread, os::os_thread)) {
+      os::start_thread(g_sampler_thread);
+    }
+    return true;
+  }
+  return false;
+}
+
+
+/////// JVM-specific columns //////////
+
+static Column* g_col_heap_committed = NULL;
+static Column* g_col_heap_used = NULL;
+
+static Column* g_col_metaspace_committed = NULL;
+static Column* g_col_metaspace_used = NULL;
+static Column* g_col_classspace_committed = NULL;
+static Column* g_col_classspace_used = NULL;
+static Column* g_col_metaspace_cap_until_gc = NULL;
+
+static Column* g_col_codecache_committed = NULL;
+
+static Column* g_col_nmt_malloc = NULL;
+
+static Column* g_col_number_of_java_threads = NULL;
+static Column* g_col_number_of_java_threads_non_demon = NULL;
+static Column* g_col_size_thread_stacks = NULL;
+static Column* g_col_number_of_java_threads_created = NULL;
+
+static Column* g_col_number_of_clds = NULL;
+static Column* g_col_number_of_anon_clds = NULL;
+
+static Column* g_col_number_of_classes = NULL;
+static Column* g_col_number_of_class_loads = NULL;
+static Column* g_col_number_of_class_unloads = NULL;
+
+//...
+
+static bool add_jvm_columns() {
+  // Order matters!
+
+  g_col_heap_committed = new MemorySizeColumn("jvm",
+      "heap", "comm", "Java Heap Size, committed");
+  g_col_heap_used = new MemorySizeColumn("jvm",
+      "heap", "used", "Java Heap Size, used");
+
+  g_col_metaspace_committed = new MemorySizeColumn("jvm",
+      "meta", "comm", "Meta Space Size (class+nonclass), committed");
+
+  g_col_metaspace_used = new MemorySizeColumn("jvm",
+      "meta", "used", "Meta Space Size (class+nonclass), used");
+
+  if (Metaspace::using_class_space()) {
+    g_col_classspace_committed = new MemorySizeColumn("jvm",
+        "meta", "csc", "Class Space Size, committed");
+    g_col_classspace_used = new MemorySizeColumn("jvm",
+        "meta", "csu", "Class Space Size, used");
+  }
+
+  g_col_metaspace_cap_until_gc = new MemorySizeColumn("jvm",
+      "meta", "gctr", "GC threshold");
+
+  g_col_codecache_committed = new MemorySizeColumn("jvm",
+      NULL, "code", "Code cache, committed");
+
+  g_col_nmt_malloc = new MemorySizeColumn("jvm",
+      NULL, "mlc", "Memory malloced by hotspot (requires NMT)");
+
+  g_col_number_of_java_threads = new PlainValueColumn("jvm",
+      "jthr", "num", "Number of java threads");
+
+  g_col_number_of_java_threads_non_demon = new PlainValueColumn("jvm",
+      "jthr", "nd", "Number of non-demon java threads");
+
+  g_col_number_of_java_threads_created = new DeltaValueColumn("jvm",
+      "jthr", "cr", "Threads created");
+
+  g_col_size_thread_stacks = new MemorySizeColumn("jvm",
+      "jthr", "st", "Total reserved size of java thread stacks");
+
+  g_col_number_of_clds = new PlainValueColumn("jvm",
+      "cldg", "num", "Classloader Data");
+
+  g_col_number_of_anon_clds = new PlainValueColumn("jvm",
+      "cldg", "anon", "Anonymous CLD");
+
+  g_col_number_of_classes = new PlainValueColumn("jvm",
+      "cls", "num", "Classes (instance + array)");
+
+  g_col_number_of_class_loads = new DeltaValueColumn("jvm",
+      "cls", "ld", "Class loaded");
+
+  g_col_number_of_class_unloads = new DeltaValueColumn("jvm",
+      "cls", "uld", "Classes unloaded");
+
+  return true;
+}
+
+
+////////// class ValueSampler and childs /////////////////
+
+template <typename T>
+static void set_value_in_record(const Column* col, record_t* r, T t) {
+  if (col != NULL) {
+    int idx = col->index();
+    assert(ColumnList::the_list()->is_valid_column_index(idx), "Invalid column index");
+    r->values[idx] = (value_t)t;
+  }
+}
+
+class AddStackSizeThreadClosure: public ThreadClosure {
+  size_t _l;
+public:
+  AddStackSizeThreadClosure() : ThreadClosure(), _l(0) {}
+  void do_thread(Thread* thread) {
+    _l += thread->stack_size();
+  }
+  size_t get() const { return _l; }
+};
+
+static size_t accumulate_thread_stack_size() {
+#if defined(LINUX) || defined(__APPLE__)
+  // Do not iterate thread list and query stack size until 8212173 is completely solved. It is solved
+  // for BSD and Linux; on the other platforms, one runs a miniscule but real risk of triggering
+  // the assert in Thread::stack_size().
+  size_t l = 0;
+  AddStackSizeThreadClosure tc;
+  {
+    MutexLocker ml(Threads_lock);
+    Threads::threads_do(&tc);
+  }
+  return tc.get();
+#else
+  return INVALID_VALUE;
+#endif
+}
+
+// Count CLDs
+class CLDCounterClosure: public CLDClosure {
+public:
+  int _cnt;
+  int _anon_cnt;
+  CLDCounterClosure() : _cnt(0), _anon_cnt(0) {}
+  void do_cld(ClassLoaderData* cld) {
+    _cnt ++;
+    if (cld->is_unsafe_anonymous()) {
+      _anon_cnt ++;
+    }
+  }
+};
+
+void sample_jvm_values(record_t* record, bool avoid_locking) {
+
+  // Heap
+  if (!avoid_locking) {
+    size_t heap_cap = 0;
+    size_t heap_used = 0;
+    const CollectedHeap* const heap = Universe::heap();
+    if (heap != NULL) {
+      MutexLocker hl(Heap_lock);
+      heap_cap = Universe::heap()->capacity();
+      heap_used = Universe::heap()->used();
+    }
+    set_value_in_record(g_col_heap_committed, record, heap_cap);
+    set_value_in_record(g_col_heap_used, record, heap_used);
+  }
+
+  // Metaspace
+  set_value_in_record(g_col_metaspace_committed, record, MetaspaceUtils::committed_bytes());
+  set_value_in_record(g_col_metaspace_used, record, MetaspaceUtils::used_bytes());
+
+  if (Metaspace::using_class_space()) {
+    set_value_in_record(g_col_classspace_committed, record, MetaspaceUtils::committed_bytes(Metaspace::ClassType));
+    set_value_in_record(g_col_classspace_used, record, MetaspaceUtils::used_bytes(Metaspace::ClassType));
+  }
+
+  set_value_in_record(g_col_metaspace_cap_until_gc, record, MetaspaceGC::capacity_until_GC());
+
+  // Code cache
+  const size_t codecache_committed = CodeCache::capacity();
+  set_value_in_record(g_col_codecache_committed, record, codecache_committed);
+
+  // NMT
+  if (!avoid_locking) {
+    size_t malloc_footprint = 0;
+    if (MemTracker::tracking_level() != NMT_off) {
+      MutexLocker locker(MemTracker::query_lock());
+      malloc_footprint = MallocMemorySummary::as_snapshot()->total();
+    }
+    set_value_in_record(g_col_nmt_malloc, record, malloc_footprint);
+  }
+
+  // Java threads
+  set_value_in_record(g_col_number_of_java_threads, record, Threads::number_of_threads());
+  set_value_in_record(g_col_number_of_java_threads_non_demon, record, Threads::number_of_non_daemon_threads());
+  set_value_in_record(g_col_number_of_java_threads_created, record, counters::g_threads_created);
+
+  // Java thread stack size
+  if (!avoid_locking) {
+    set_value_in_record(g_col_size_thread_stacks, record, accumulate_thread_stack_size());
+  }
+
+  // CLDG
+  if (!avoid_locking) {
+    CLDCounterClosure cl;
+    {
+      MutexLocker lck(ClassLoaderDataGraph_lock);
+      ClassLoaderDataGraph::cld_do(&cl);
+    }
+    set_value_in_record(g_col_number_of_clds, record, cl._cnt);
+    set_value_in_record(g_col_number_of_anon_clds, record, cl._anon_cnt);
+  }
+
+  // Classes
+  set_value_in_record(g_col_number_of_classes, record,
+      ClassLoaderDataGraph::num_instance_classes() + ClassLoaderDataGraph::num_array_classes());
+  set_value_in_record(g_col_number_of_class_loads, record, counters::g_classes_loaded);
+  set_value_in_record(g_col_number_of_class_unloads, record, counters::g_classes_unloaded);
+}
+
+bool initialize() {
+
+  if (!ColumnList::initialize()) {
+    return false;
+  }
+
+  // Order matters. First platform columns, then jvm columns.
+  if (!platform_columns_initialize()) {
+    return false;
+  }
+
+  if (!add_jvm_columns()) {
+    return false;
+  }
+
+  // -- Now the number of columns is known (and fixed). --
+
+  if (!initialize_widths_buffer()) {
+    return false;
+  }
+
+  if (!initialize_space_for_now_record()) {
+    return false;
+  }
+
+  if (!RecordTables::initialize()) {
+    return false;
+  }
+
+  if (!initialize_sampler_thread()) {
+    return false;
+  }
+
+  return true;
+
+}
+
+void cleanup() {
+  if (g_sampler_thread != NULL) {
+    g_sampler_thread->stop();
+  }
+}
+
+void print_report(outputStream* st, const print_info_t* pi) {
+
+  st->print("stathist:");
+
+  if (ColumnList::the_list() == NULL) {
+    st->print_cr(" (unavailable)");
+    return;
+  }
+
+  st->cr();
+
+  static const print_info_t default_settings = {
+      false, // raw
+      false, // cvs
+      false, // omit_legend
+      true   // avoid_sampling
+  };
+
+  if (pi == NULL) {
+    pi = &default_settings;
+  }
+
+  // Print legend at the top (omit if suppressed on command line, or in cvs mode).
+  if (pi->no_legend == false && pi->cvs == false) {
+    print_legend(st, pi);
+    st->cr();
+  }
+
+  CLocaleMark locale_mark; // Print all numbers with locale "C"
+
+  record_t* values_now = NULL;
+  if (!pi->avoid_sampling) {
+    // Sample the current values (not when reporting errors, since we do not want to risk secondary errors).
+    values_now = g_record_now;
+    values_now->timestamp = 0; // means "Now"
+    sample_values(values_now, true);
+  }
+
+  RecordTables::the_tables()->print_all(st, pi, values_now);
+
+}
+
+} // namespace StatisticsHistory
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/services/stathist.hpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef HOTSPOT_SHARE_SERVICES_STATHIST_HPP
+#define HOTSPOT_SHARE_SERVICES_STATHIST_HPP
+
+#include "utilities/globalDefinitions.hpp"
+
+class outputStream;
+
+namespace StatisticsHistory {
+
+  bool initialize();
+  void cleanup();
+
+  struct print_info_t {
+    bool raw;
+    bool cvs;
+    // Omit printing a legend.
+    bool no_legend;
+    // Normally, when we print a report, we sample the current values too and print it atop of the table.
+    // We may want to avoid that, e.g. during error handling.
+    bool avoid_sampling;
+
+    size_t scale;
+  };
+
+  // If no print info is given (print_info == NULL), we print with default settings
+  void print_report(outputStream* st, const print_info_t* print_info = NULL);
+
+  // These are counters for the statistics history. Ideally, they would live
+  // inside their thematical homes, e.g. thread.cpp or classLoaderDataGraph.cpp,
+  // however since this is unlikely ever to be brought upstream we keep this separate
+  // to easy maintenance.
+
+  namespace counters {
+    void inc_classes_loaded(size_t count);
+    void inc_classes_unloaded(size_t count);
+    void inc_threads_created(size_t count);
+  };
+
+};
+
+#endif /* HOTSPOT_SHARE_SERVICES_STATHIST_HPP */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/services/stathistDCmd.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+
+#include "precompiled.hpp"
+
+#include "memory/resourceArea.hpp"
+#include "services/stathist.hpp"
+#include "services/stathistDCmd.hpp"
+#include "utilities/ostream.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+namespace StatisticsHistory {
+
+StatHistDCmd::StatHistDCmd(outputStream* output, bool heap)
+  : DCmdWithParser(output, heap),
+    _scale("scale", "Memory usage in which to scale. Valid values are: k, m, g (fixed scale) "
+           "or \"dynamic\" for a dynamically chosen scale.",
+           "STRING", false, "dynamic"),
+    _cvs("cvs", "CVS format.", "BOOLEAN", false, "false"),
+    _no_legend("no-legend", "Omit legend.", "BOOLEAN", false, "false")
+#ifdef ASSERT
+    , _raw("raw", "Print raw values (debug only).", "BOOLEAN", false, "false")
+#endif
+{
+  _dcmdparser.add_dcmd_option(&_scale);
+  _dcmdparser.add_dcmd_option(&_no_legend);
+  DEBUG_ONLY(_dcmdparser.add_dcmd_option(&_raw);)
+  _dcmdparser.add_dcmd_option(&_cvs);
+}
+
+int StatHistDCmd::num_arguments() {
+  ResourceMark rm;
+  StatHistDCmd* dcmd = new StatHistDCmd(NULL, false);
+  if (dcmd != NULL) {
+    DCmdMark mark(dcmd);
+    return dcmd->_dcmdparser.num_arguments();
+  } else {
+    return 0;
+  }
+}
+
+static bool scale_from_name(const char* scale, size_t* out) {
+  if (strcasecmp(scale, "dynamic") == 0) {
+    *out = 0;
+  } else if (strcasecmp(scale, "kb") == 0 || strcasecmp(scale, "k") == 0) {
+    *out = K;
+  } else if (strcasecmp(scale, "mb") == 0 || strcasecmp(scale, "m") == 0) {
+    *out = M;
+  } else if (strcasecmp(scale, "gb") == 0 || strcasecmp(scale, "g") == 0) {
+    *out = G;
+  } else {
+    return false; // Invalid value
+  }
+  return true;
+}
+
+void StatHistDCmd::execute(DCmdSource source, TRAPS) {
+  print_info_t pi;
+  if (!scale_from_name(_scale.value(), &(pi.scale))) {
+    output()->print_cr("Invalid scale: \"%s\".", _scale.value());
+    return;
+  }
+  DEBUG_ONLY(pi.raw = _raw.value();)
+  pi.cvs = _cvs.value();
+  pi.no_legend = _no_legend.value();
+  pi.avoid_sampling = false;
+
+  StatisticsHistory::print_report(output(), &pi);
+}
+
+}; // namespace StatisticsHistory
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/services/stathistDCmd.hpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+
+#ifndef HOTSPOT_SHARE_SERVICES_STATHIST_INTERNALS_HPP
+#define HOTSPOT_SHARE_SERVICES_STATHIST_INTERNALS_HPP
+
+#include "services/diagnosticCommand.hpp"
+
+namespace StatisticsHistory {
+
+class StatHistDCmd : public DCmdWithParser {
+protected:
+  DCmdArgument<char*> _scale;
+  DCmdArgument<bool> _cvs;
+  DCmdArgument<bool> _no_legend;
+#ifdef ASSERT
+  DCmdArgument<bool> _raw;
+#endif
+public:
+  StatHistDCmd(outputStream* output, bool heap);
+  static const char* name() {
+    return "VM.stathist";
+  }
+  static const char* description() {
+    return "Provide statistics history.";
+  }
+  static const char* impact() {
+    return "Low.";
+  }
+  static const JavaPermission permission() {
+    JavaPermission p = {"java.lang.management.ManagementPermission",
+                        "monitor", NULL};
+    return p;
+  }
+  static int num_arguments();
+  virtual void execute(DCmdSource source, TRAPS);
+};
+
+}; // namespace StatisticsHistory
+
+#endif /* HOTSPOT_SHARE_SERVICES_STATHIST_INTERNALS_HPP */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/services/stathist_internals.hpp	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef HOTSPOT_SHARE_SERVICES_STATHIST_INTERNALS_HPP
+#define HOTSPOT_SHARE_SERVICES_STATHIST_INTERNALS_HPP
+
+#include "memory/allocation.hpp"
+#include "services/stathist.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+
+namespace StatisticsHistory {
+
+  typedef uint64_t value_t;
+#define INVALID_VALUE   ((value_t)UINT64_MAX)
+
+  struct record_t {
+    time_t timestamp;
+    value_t values[1]; // var sized
+  };
+
+  class ColumnList;
+
+  class Column: public CHeapObj<mtInternal> {
+    friend class ColumnList;
+
+    const char* const _category;
+    const char* const _header; // optional. May be NULL.
+    const char* const _name;
+    const char* const _description;
+
+    // The following members are fixed by ColumnList when the Column is added to it.
+    Column* _next;  // next column in table
+    int _idx;       // position in table
+    int _idx_cat;   // position in category
+    int _idx_hdr;   // position under its header (if any, 0 otherwise)
+
+  protected:
+
+    Column(const char* category, const char* header, const char* name, const char* description);
+
+    // Child classes implement this.
+    // output stream can be NULL; in that case, method shall return number of characters it would have printed.
+    virtual int do_print(outputStream* os, value_t value,
+        value_t last_value, int last_value_age, const print_info_t* pi) const = 0;
+
+  public:
+
+    const char* category() const      { return _category; }
+    const char* header() const        { return _header; }
+    const char* name() const          { return _name; }
+    const char* description() const   { return _description; }
+
+    void print_value(outputStream* os, value_t value, value_t last_value,
+        int last_value_age, int min_width, const print_info_t* pi) const;
+
+    // Returns the number of characters this value needs to be printed.
+    int calc_print_size(value_t value, value_t last_value,
+        int last_value_age, const print_info_t* pi) const;
+
+    // Returns the index (the position in the table) of this column.
+    int index() const                 { return _idx; }
+    int index_within_category_section() const { return _idx_cat; }
+    int index_within_header_section() const   { return _idx_hdr; }
+
+    const Column* next () const       { return _next; }
+
+    virtual bool is_memory_size() const { return false; }
+    virtual bool is_delta() const { return false; }
+
+
+  };
+
+  // Some standard column types
+
+  class PlainValueColumn: public Column {
+    int do_print(outputStream* os, value_t value, value_t last_value,
+        int last_value_age, const print_info_t* pi) const;
+  public:
+    PlainValueColumn(const char* category, const char* header, const char* name, const char* description)
+      : Column(category, header, name, description)
+    {}
+  };
+
+  class DeltaValueColumn: public Column {
+    const bool _show_only_positive;
+    int do_print(outputStream* os, value_t value, value_t last_value,
+        int last_value_age, const print_info_t* pi) const;
+  public:
+    // only_positive: only positive deltas are shown, negative deltas are supressed
+    DeltaValueColumn(const char* category, const char* header, const char* name, const char* description,
+        bool show_only_positive = true)
+      : Column(category, header, name, description)
+      , _show_only_positive(show_only_positive)
+    {}
+    bool is_delta() const { return true; }
+  };
+
+  class MemorySizeColumn: public Column {
+    int do_print(outputStream* os, value_t value, value_t last_value,
+        int last_value_age, const print_info_t* pi) const;
+  public:
+    MemorySizeColumn(const char* category, const char* header, const char* name, const char* description)
+      : Column(category, header, name, description)
+    {}
+    bool is_memory_size() const { return true; }
+  };
+
+  class DeltaMemorySizeColumn: public Column {
+    int do_print(outputStream* os, value_t value, value_t last_value,
+        int last_value_age, const print_info_t* pi) const;
+  public:
+    DeltaMemorySizeColumn(const char* category, const char* header, const char* name, const char* description)
+      : Column(category, header, name, description)
+    {}
+    bool is_memory_size() const { return true; }
+    bool is_delta() const { return true; }
+  };
+
+  class ColumnList: public CHeapObj<mtInternal> {
+
+    Column* _first, *_last;
+    int _num_columns;
+
+    static ColumnList* _the_list;
+
+  public:
+
+    ColumnList()
+      : _first(NULL), _last(NULL), _num_columns(0)
+    {}
+
+    const Column* first() const { return _first; }
+    int num_columns() const     { return _num_columns; }
+
+    void add_column(Column* column);
+
+    static ColumnList* the_list () { return _the_list; }
+
+    static bool initialize();
+
+#ifdef ASSERT
+    bool is_valid_column_index(int idx) {
+      return idx >= 0 && idx < _num_columns;
+    }
+#endif
+
+  };
+
+  // Implemented by platform specific
+  bool platform_columns_initialize();
+
+  void sample_platform_values(record_t* record);
+  void sample_jvm_values(record_t* record, bool avoid_locking);
+
+}; // namespace StatisticsHistory
+
+#endif /* HOTSPOT_SHARE_SERVICES_STATHIST_INTERNALS_HPP */
--- a/src/hotspot/share/utilities/vmError.cpp	Thu Feb 28 13:37:03 2019 +0800
+++ b/src/hotspot/share/utilities/vmError.cpp	Fri Sep 07 07:52:35 2018 +0200
@@ -43,6 +43,8 @@
 #include "runtime/vmOperations.hpp"
 #include "runtime/vm_version.hpp"
 #include "runtime/flags/jvmFlag.hpp"
+// SapMachine 2019-02-20 : stathist
+#include "services/stathist.hpp"
 #include "services/memTracker.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/decoder.hpp"
@@ -984,6 +986,16 @@
        MemTracker::error_report(st);
      }
 
+  // SapMachine 2019-02-20 : stathist
+  STEP("Statistics History")
+     if (_verbose) {
+       static const StatisticsHistory::print_info_t settings = {
+           false, false, false, // omit_legend
+           true   // avoid_sampling to be safe
+       };
+       StatisticsHistory::print_report(st, &settings);
+     }
+
   STEP("printing system")
 
      if (_verbose) {
@@ -1155,6 +1167,10 @@
 
   MemTracker::error_report(st);
 
+  // SapMachine 2019-02-20 : stathist
+  // STEP("Statistics History")
+  StatisticsHistory::print_report(st, NULL);
+
   // STEP("printing system")
 
   st->cr();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/dcmd/vm/StatHistTest.java	Fri Sep 07 07:52:35 2018 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Test of diagnostic command VM.stathist
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.compiler
+ *          java.management
+ *          jdk.internal.jvmstat/sun.jvmstat.monitor
+ * @run testng StatHistTest
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.dcmd.CommandExecutor;
+import jdk.test.lib.dcmd.JMXExecutor;
+
+public class StatHistTest {
+
+    public void run(CommandExecutor executor) {
+        OutputAnalyzer output = executor.execute("VM.stathist");
+        output.shouldContain("--jvm--");
+        output.shouldContain("--heap--");
+        output.shouldContain("--meta--");
+        output = executor.execute("VM.stathist cvs");
+        output.shouldContain("heap-comm,heap-used,meta-comm,meta-used");
+    }
+
+    @Test
+    public void jmx() {
+        run(new JMXExecutor());
+    }
+
+}
+