From 98d198c7017b14adf3e5ff852cfbd29e6172fa10 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 11 Nov 2015 22:47:21 -0800 Subject: [PATCH] Export standard JVM and JGit metrics Add support for constant metrics. These are exported values that are computed only once per process and cannot change without the JVM shutting down and starting back up again. Export proc/birth_timestamp as build/label to describe when the process launched and what version it is. These are constants. Add support for CallbackMetric1, allowing for some JVM GC data to be exported. These can be useful to track Java GC overheads over time. Export JGit block cache metrics along with high level JVM memory usage. Change-Id: I9bb24a466eab99cf93358b105ff0c7333bd78ea2 --- .../gerrit/metrics/CallbackMetric1.java | 34 +++ .../google/gerrit/metrics/Description.java | 16 ++ .../gerrit/metrics/DisabledMetricMaker.java | 10 + .../google/gerrit/metrics/MetricMaker.java | 24 ++ .../metrics/dropwizard/BucketedCallback.java | 124 ++++++++++ .../dropwizard/CallbackMetricGlue.java | 19 ++ .../dropwizard/CallbackMetricImpl0.java | 29 ++- .../dropwizard/CallbackMetricImpl1.java | 67 ++++++ .../dropwizard/DropWizardMetricMaker.java | 46 ++-- .../gerrit/metrics/dropwizard/MetricJson.java | 2 + .../gerrit/metrics/proc/JGitMetricModule.java | 53 ++++ .../gerrit/metrics/proc/MetricModule.java | 43 ++++ .../gerrit/metrics/proc/ProcMetricModule.java | 226 ++++++++++++++++++ .../server/plugins/PluginMetricMaker.java | 10 + 14 files changed, 674 insertions(+), 29 deletions(-) create mode 100644 gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric1.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricGlue.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/metrics/proc/JGitMetricModule.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/metrics/proc/MetricModule.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/metrics/proc/ProcMetricModule.java diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric1.java new file mode 100644 index 0000000000..03ae198032 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric1.java @@ -0,0 +1,34 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.metrics; + +/** + * Metric whose value is supplied when the trigger is invoked. + * + * @param type of the field. + * @param type of the metric value, typically Integer or Long. + */ +public abstract class CallbackMetric1 implements CallbackMetric { + /** + * Supply the current value of the metric. + * + * @param field1 bucket to increment. + * @param value current value. + */ + public abstract void set(F1 field1, V value); + + /** Ensure a zeroed metric is created for the field value. */ + public abstract void forceCreate(F1 field1); +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Description.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Description.java index fe244ed4fc..43e5c8a91f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/Description.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Description.java @@ -28,6 +28,7 @@ public class Description { public static final String CUMULATIVE = "CUMULATIVE"; public static final String RATE = "RATE"; public static final String GAUGE = "GAUGE"; + public static final String CONSTANT = "CONSTANT"; public static final String FIELD_ORDERING = "FIELD_ORDERING"; public static final String TRUE_VALUE = "1"; @@ -76,6 +77,16 @@ public class Description { return this; } + /** + * Mark the value as constant for the life of this process. Typically used for + * software versions, command line arguments, etc. that cannot change without + * a process restart. + */ + public Description setConstant() { + annotations.put(CONSTANT, TRUE_VALUE); + return this; + } + /** * Indicates the metric may be usefully interpreted as a count over short * periods of time, such as request arrival rate. May only be applied to a @@ -111,6 +122,11 @@ public class Description { return this; } + /** True if the metric value never changes after startup. */ + public boolean isConstant() { + return TRUE_VALUE.equals(annotations.get(CONSTANT)); + } + /** True if the metric may be interpreted as a rate over time. */ public boolean isRate() { return TRUE_VALUE.equals(annotations.get(RATE)); diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/DisabledMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/DisabledMetricMaker.java index 49954a481e..14b1fff191 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/DisabledMetricMaker.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/DisabledMetricMaker.java @@ -100,6 +100,16 @@ public class DisabledMetricMaker extends MetricMaker { }; } + @Override + public CallbackMetric1 newCallbackMetric(String name, + Class valueClass, Description desc, Field field1) { + return new CallbackMetric1() { + @Override public void set(F1 field1, V value) {} + @Override public void forceCreate(F1 field1) {} + @Override public void remove() {} + }; + } + @Override public RegistrationHandle newTrigger(Set> metrics, Runnable trigger) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/MetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/MetricMaker.java index 55844b165c..03fc3336a0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/MetricMaker.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/MetricMaker.java @@ -46,6 +46,27 @@ public abstract class MetricMaker { String name, Description desc, Field field1, Field field2, Field field3); + /** + * Constant value that does not change. + * + * @param name unique name of the metric. + * @param value only value of the metric. + * @param desc description of the metric. + */ + public void newConstantMetric(String name, final V value, Description desc) { + desc.setConstant(); + + @SuppressWarnings("unchecked") + Class type = (Class) value.getClass(); + final CallbackMetric0 metric = newCallbackMetric(name, type, desc); + newTrigger(metric, new Runnable() { + @Override + public void run() { + metric.set(value); + } + }); + } + /** * Instantaneous reading of a value. * @@ -80,6 +101,9 @@ public abstract class MetricMaker { /** Instantaneous reading of a single value. */ public abstract CallbackMetric0 newCallbackMetric( String name, Class valueClass, Description desc); + public abstract CallbackMetric1 newCallbackMetric( + String name, Class valueClass, Description desc, + Field field1); /** Connect logic to populate a previously created {@link CallbackMetric}. */ public RegistrationHandle newTrigger(CallbackMetric metric1, Runnable trigger) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java new file mode 100644 index 0000000000..43246d7ccf --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java @@ -0,0 +1,124 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** Abstract callback metric broken down into buckets. */ +abstract class BucketedCallback implements BucketedMetric { + private final DropWizardMetricMaker metrics; + private final MetricRegistry registry; + private final String name; + private final Description.FieldOrdering ordering; + protected final Field[] fields; + private final V zero; + private final Map cells; + protected volatile Runnable trigger; + + BucketedCallback(DropWizardMetricMaker metrics, MetricRegistry registry, + String name, Class valueType, Description desc, Field... fields) { + this.metrics = metrics; + this.registry = registry; + this.name = name; + this.ordering = desc.getFieldOrdering(); + this.fields = fields; + this.zero = CallbackMetricImpl0.zeroFor(valueType); + this.cells = new ConcurrentHashMap<>(); + } + + void doRemove() { + for (Object key : cells.keySet()) { + registry.remove(submetric(key)); + } + metrics.remove(name); + } + + ValueGauge getOrCreate(Object f1, Object f2) { + return getOrCreate(ImmutableList.of(f1, f2)); + } + + ValueGauge getOrCreate(Object f1, Object f2, Object f3) { + return getOrCreate(ImmutableList.of(f1, f2, f3)); + } + + ValueGauge getOrCreate(Object key) { + ValueGauge c = cells.get(key); + if (c != null) { + return c; + } + + synchronized (cells) { + c = cells.get(key); + if (c == null) { + c = new ValueGauge(); + registry.register(submetric(key), c); + cells.put(key, c); + } + return c; + } + } + + private String submetric(Object key) { + return DropWizardMetricMaker.name(ordering, name, name(key)); + } + + abstract String name(Object key); + + @Override + public Metric getTotal() { + return null; + } + + @Override + public Field[] getFields() { + return fields; + } + + @Override + public Map getCells() { + return Maps.transformValues( + cells, + new Function () { + @Override + public Metric apply(ValueGauge in) { + return in; + } + }); + } + + final class ValueGauge implements Gauge { + volatile V value = zero; + + @Override + public V getValue() { + Runnable t = trigger; + if (t != null) { + t.run(); + } + return value; + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricGlue.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricGlue.java new file mode 100644 index 0000000000..69c4a0d7be --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricGlue.java @@ -0,0 +1,19 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.metrics.dropwizard; + +interface CallbackMetricGlue { + void register(Runnable trigger); +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl0.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl0.java index 7ad2970b99..31804c7647 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl0.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl0.java @@ -16,7 +16,11 @@ package com.google.gerrit.metrics.dropwizard; import com.google.gerrit.metrics.CallbackMetric0; -class CallbackMetricImpl0 extends CallbackMetric0 { +import com.codahale.metrics.MetricRegistry; + +class CallbackMetricImpl0 + extends CallbackMetric0 + implements CallbackMetricGlue { @SuppressWarnings("unchecked") static V zeroFor(Class valueClass) { if (valueClass == Integer.class) { @@ -37,10 +41,15 @@ class CallbackMetricImpl0 extends CallbackMetric0 { } } - final String name; - private V value; + private final DropWizardMetricMaker metrics; + private final MetricRegistry registry; + private final String name; + private volatile V value; - CallbackMetricImpl0(String name, Class valueType) { + CallbackMetricImpl0(DropWizardMetricMaker metrics, MetricRegistry registry, + String name, Class valueType) { + this.metrics = metrics; + this.registry = registry; this.name = name; this.value = zeroFor(valueType); } @@ -52,16 +61,18 @@ class CallbackMetricImpl0 extends CallbackMetric0 { @Override public void remove() { - // Triggers register and remove the metric. + metrics.remove(name); + registry.remove(name); } - com.codahale.metrics.Gauge gauge(final Runnable trigger) { - return new com.codahale.metrics.Gauge() { + @Override + public void register(final Runnable trigger) { + registry.register(name, new com.codahale.metrics.Gauge() { @Override public V getValue() { trigger.run(); return value; } - }; + }); } -} \ No newline at end of file +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java new file mode 100644 index 0000000000..9278e700db --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java @@ -0,0 +1,67 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; + +import com.codahale.metrics.MetricRegistry; + +/** Optimized version of {@link BucketedCallback} for single dimension. */ +class CallbackMetricImpl1 extends BucketedCallback { + CallbackMetricImpl1(DropWizardMetricMaker metrics, MetricRegistry registry, + String name, Class valueClass, Description desc, Field field1) { + super(metrics, registry, name, valueClass, desc, field1); + } + + CallbackMetric1 create() { + return new Impl1(); + } + + private final class Impl1 + extends CallbackMetric1 + implements CallbackMetricGlue { + @Override + public void set(F1 field1, V value) { + getOrCreate(field1).value = value; + } + + @Override + public void forceCreate(F1 field1) { + getOrCreate(field1); + } + + @Override + public void register(Runnable t) { + trigger = t; + } + + @Override + public void remove() { + doRemove(); + } + } + + @Override + String name(Object field1) { + @SuppressWarnings("unchecked") + Function fmt = + (Function) fields[0].formatter(); + + return fmt.apply(field1).replace('/', '-'); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java index 336bf9eb06..58abed8a4f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java @@ -24,18 +24,21 @@ import com.google.gerrit.extensions.registration.RegistrationHandle; import com.google.gerrit.extensions.restapi.RestApiModule; import com.google.gerrit.metrics.CallbackMetric; import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.CallbackMetric1; import com.google.gerrit.metrics.Counter0; import com.google.gerrit.metrics.Counter1; import com.google.gerrit.metrics.Counter2; import com.google.gerrit.metrics.Counter3; import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.FieldOrdering; import com.google.gerrit.metrics.Field; import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.Timer0; import com.google.gerrit.metrics.Timer1; import com.google.gerrit.metrics.Timer2; import com.google.gerrit.metrics.Timer3; -import com.google.gerrit.metrics.Description.FieldOrdering; +import com.google.gerrit.metrics.proc.JGitMetricModule; +import com.google.gerrit.metrics.proc.ProcMetricModule; import com.google.inject.Inject; import com.google.inject.Scopes; import com.google.inject.Singleton; @@ -64,6 +67,9 @@ public class DropWizardMetricMaker extends MetricMaker { bind(DropWizardMetricMaker.class).in(Scopes.SINGLETON); bind(MetricMaker.class).to(DropWizardMetricMaker.class); + install(new ProcMetricModule()); + install(new JGitMetricModule()); + DynamicMap.mapOf(binder(), METRIC_KIND); child(CONFIG_KIND, "metrics").to(MetricsCollection.class); get(METRIC_KIND).to(GetMetric.class); @@ -137,6 +143,7 @@ public class DropWizardMetricMaker extends MetricMaker { } private static void checkCounterDescription(Description desc) { + checkArgument(!desc.isConstant(), "counters must not be constant"); checkArgument(!desc.isGauge(), "counters must not be gauge"); } @@ -200,6 +207,7 @@ public class DropWizardMetricMaker extends MetricMaker { } private static void checkTimerDescription(Description desc) { + checkArgument(!desc.isConstant(), "timer must not be constant"); checkArgument(!desc.isGauge(), "timer must not be a gauge"); checkArgument(!desc.isRate(), "timer must not be a rate"); checkArgument(desc.isCumulative(), "timer must be cumulative"); @@ -214,37 +222,35 @@ public class DropWizardMetricMaker extends MetricMaker { public CallbackMetric0 newCallbackMetric( String name, Class valueClass, Description desc) { define(name, desc); - return new CallbackMetricImpl0<>(name, valueClass); + return new CallbackMetricImpl0<>(this, registry, name, valueClass); + } + + @Override + public CallbackMetric1 newCallbackMetric( + String name, Class valueClass, Description desc, Field field1) { + CallbackMetricImpl1 m = new CallbackMetricImpl1<>(this, registry, + name, valueClass, desc, field1); + define(name, desc); + bucketed.put(name, m); + return m.create(); } @Override public synchronized RegistrationHandle newTrigger( Set> metrics, Runnable trigger) { - if (metrics.size() > 1) { - trigger = new CallbackGroup(trigger); - } + trigger = new CallbackGroup(trigger); for (CallbackMetric m : metrics) { - CallbackMetricImpl0 metric = (CallbackMetricImpl0) m; - if (registry.getMetrics().containsKey(metric.name)) { - throw new IllegalStateException(String.format( - "metric %s already configured", metric.name)); - } - } - - final List names = new ArrayList<>(metrics.size()); - for (CallbackMetric m : metrics) { - CallbackMetricImpl0 metric = (CallbackMetricImpl0) m; - registry.register(metric.name, metric.gauge(trigger)); - names.add(metric.name); + ((CallbackMetricGlue) m).register(trigger); } + trigger.run(); + final List> all = new ArrayList<>(metrics); return new RegistrationHandle() { @Override public void remove() { - for (String name : names) { - descriptions.remove(name); - registry.remove(name); + for (CallbackMetric m : all) { + m.remove(); } } }; diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricJson.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricJson.java index f43dd6f899..3dfcdfe6ea 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricJson.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricJson.java @@ -35,6 +35,7 @@ import java.util.TreeMap; class MetricJson { String description; String unit; + Boolean constant; Boolean rate; Boolean gauge; Boolean cumulative; @@ -65,6 +66,7 @@ class MetricJson { if (!dataOnly) { description = atts.get(Description.DESCRIPTION); unit = atts.get(Description.UNIT); + constant = toBool(atts, Description.CONSTANT); rate = toBool(atts, Description.RATE); gauge = toBool(atts, Description.GAUGE); cumulative = toBool(atts, Description.CUMULATIVE); diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/JGitMetricModule.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/JGitMetricModule.java new file mode 100644 index 0000000000..7bf69b8cd9 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/JGitMetricModule.java @@ -0,0 +1,53 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.metrics.proc; + +import com.google.common.base.Supplier; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Description.Units; + +import org.eclipse.jgit.internal.storage.file.WindowCacheStatAccessor; + +public class JGitMetricModule extends MetricModule { + @Override + protected void configure(MetricMaker metrics) { + metrics.newCallbackMetric( + "jgit/block_cache/cache_used", + Long.class, + new Description("Bytes of memory retained in JGit block cache.") + .setGauge() + .setUnit(Units.BYTES), + new Supplier() { + @Override + public Long get() { + return WindowCacheStatAccessor.getOpenBytes(); + } + }); + + metrics.newCallbackMetric( + "jgit/block_cache/open_files", + Integer.class, + new Description("File handles held open by JGit block cache.") + .setGauge() + .setUnit("fds"), + new Supplier() { + @Override + public Integer get() { + return WindowCacheStatAccessor.getOpenFiles(); + } + }); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/MetricModule.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/MetricModule.java new file mode 100644 index 0000000000..c556ee4e08 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/MetricModule.java @@ -0,0 +1,43 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.metrics.proc; + +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.gerrit.metrics.MetricMaker; +import com.google.inject.Inject; + +/** Guice module to configure metrics on server startup. */ +public abstract class MetricModule extends LifecycleModule { + /** Configure metrics during server startup. */ + protected abstract void configure(MetricMaker metrics); + + @Override + protected void configure() { + listener().toInstance(new LifecycleListener() { + @Inject + MetricMaker metrics; + + @Override + public void start() { + configure(metrics); + } + + @Override + public void stop() { + } + }); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/ProcMetricModule.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/ProcMetricModule.java new file mode 100644 index 0000000000..53b860c20e --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/ProcMetricModule.java @@ -0,0 +1,226 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.metrics.proc; + +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.common.Version; +import com.google.gerrit.metrics.CallbackMetric; +import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.Units; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; + +import com.sun.management.OperatingSystemMXBean; +import com.sun.management.UnixOperatingSystemMXBean; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.ThreadMXBean; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("restriction") +public class ProcMetricModule extends MetricModule { + @Override + protected void configure(MetricMaker metrics) { + buildLabel(metrics); + procUptime(metrics); + procCpuUsage(metrics); + procJvmGc(metrics); + procJvmMemory(metrics); + procJvmThread(metrics); + } + + private void buildLabel(MetricMaker metrics) { + metrics.newConstantMetric( + "build/label", + Strings.nullToEmpty(Version.getVersion()), + new Description("Version of Gerrit server software")); + } + + private void procUptime(MetricMaker metrics) { + metrics.newConstantMetric( + "proc/birth_timestamp", + Long.valueOf(TimeUnit.MILLISECONDS.toMicros( + System.currentTimeMillis())), + new Description("Time at which the process started") + .setUnit(Units.MICROSECONDS)); + + metrics.newCallbackMetric( + "proc/uptime", + Long.class, + new Description("Uptime of this process") + .setUnit(Units.MILLISECONDS), + new Supplier() { + @Override + public Long get() { + return ManagementFactory.getRuntimeMXBean().getUptime(); + } + }); + } + + private void procCpuUsage(MetricMaker metrics) { + final OperatingSystemMXBean sys = + (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + if (sys.getProcessCpuTime() != -1) { + metrics.newCallbackMetric( + "proc/cpu/usage", + Double.class, + new Description("CPU time used by the process") + .setCumulative() + .setUnit(Units.SECONDS), + new Supplier() { + @Override + public Double get() { + return sys.getProcessCpuTime() / 1e9; + } + }); + } + if (sys instanceof UnixOperatingSystemMXBean) { + final UnixOperatingSystemMXBean unix = (UnixOperatingSystemMXBean) sys; + if (unix.getOpenFileDescriptorCount() != -1) { + metrics.newCallbackMetric( + "proc/num_open_fds", + Long.class, + new Description("Number of open file descriptors") + .setGauge() + .setUnit("fds"), + new Supplier() { + @Override + public Long get() { + return unix.getOpenFileDescriptorCount(); + } + }); + } + } + } + + private void procJvmMemory(MetricMaker metrics) { + final CallbackMetric0 heapCommitted = metrics.newCallbackMetric( + "proc/jvm/memory/heap_committed", + Long.class, + new Description("Amount of memory guaranteed for user objects.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0 heapUsed = metrics.newCallbackMetric( + "proc/jvm/memory/heap_used", + Long.class, + new Description("Amount of memory holding user objects.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0 nonHeapCommitted = metrics.newCallbackMetric( + "proc/jvm/memory/non_heap_committed", + Long.class, + new Description("Amount of memory guaranteed for classes, etc.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0 nonHeapUsed = metrics.newCallbackMetric( + "proc/jvm/memory/non_heap_used", + Long.class, + new Description("Amount of memory holding classes, etc.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0 objectPendingFinalizationCount = + metrics.newCallbackMetric( + "proc/jvm/memory/object_pending_finalization_count", + Integer.class, + new Description("Approximate number of objects needing finalization.") + .setGauge() + .setUnit("objects")); + + final MemoryMXBean memory = ManagementFactory.getMemoryMXBean(); + metrics.newTrigger( + ImmutableSet.> of( + heapCommitted, heapUsed, nonHeapCommitted, + nonHeapUsed, objectPendingFinalizationCount), + new Runnable() { + @Override + public void run() { + try { + MemoryUsage stats = memory.getHeapMemoryUsage(); + heapCommitted.set(stats.getCommitted()); + heapUsed.set(stats.getUsed()); + } catch (IllegalArgumentException e) { + // MXBean may throw due to a bug in Java 7; ignore. + } + + MemoryUsage stats = memory.getNonHeapMemoryUsage(); + nonHeapCommitted.set(stats.getCommitted()); + nonHeapUsed.set(stats.getUsed()); + + objectPendingFinalizationCount.set( + memory.getObjectPendingFinalizationCount()); + } + }); + } + + private void procJvmGc(MetricMaker metrics) { + final CallbackMetric1 gcCount = metrics.newCallbackMetric( + "proc/jvm/gc/count", + Long.class, + new Description("Number of GCs").setCumulative(), + Field.ofString("gc_name", "The name of the garbage collector")); + + final CallbackMetric1 gcTime = metrics.newCallbackMetric( + "proc/jvm/gc/time", + Long.class, + new Description("Approximate accumulated GC elapsed time") + .setCumulative() + .setUnit(Units.MILLISECONDS), + Field.ofString("gc_name", "The name of the garbage collector")); + + metrics.newTrigger(gcCount, gcTime, new Runnable() { + @Override + public void run() { + for (GarbageCollectorMXBean gc : ManagementFactory + .getGarbageCollectorMXBeans()) { + long count = gc.getCollectionCount(); + if (count != -1) { + gcCount.set(gc.getName(), count); + } + long time = gc.getCollectionTime(); + if (time != -1) { + gcTime.set(gc.getName(), time); + } + } + } + }); + } + + private void procJvmThread(MetricMaker metrics) { + final ThreadMXBean thread = ManagementFactory.getThreadMXBean(); + metrics.newCallbackMetric( + "proc/jvm/thread/num_live", + Integer.class, + new Description("Current live thread count") + .setGauge() + .setUnit("threads"), + new Supplier() { + @Override + public Integer get() { + return thread.getThreadCount(); + } + }); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginMetricMaker.java index 8bf78b5508..3cbb0f5d38 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginMetricMaker.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginMetricMaker.java @@ -18,6 +18,7 @@ import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.registration.RegistrationHandle; import com.google.gerrit.metrics.CallbackMetric; import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.CallbackMetric1; import com.google.gerrit.metrics.Counter0; import com.google.gerrit.metrics.Counter1; import com.google.gerrit.metrics.Counter2; @@ -124,6 +125,15 @@ class PluginMetricMaker extends MetricMaker implements LifecycleListener { return m; } + @Override + public CallbackMetric1 newCallbackMetric(String name, + Class valueClass, Description desc, Field field1) { + CallbackMetric1 m = + root.newCallbackMetric(prefix + name, valueClass, desc, field1); + cleanup.add(m); + return m; + } + @Override public RegistrationHandle newTrigger(Set> metrics, Runnable trigger) {