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) {