From 9fb00df8bcfa37188bf1dba455ae75eba7bb612e Mon Sep 17 00:00:00 2001 From: "yiming.xu" <100650920@qq.com> Date: Thu, 3 Aug 2017 10:41:49 +0800 Subject: [PATCH] KYLIN-2714 kylin should add performance log --- kylin/core-common/pom.xml | 17 + .../org/apache/kylin/common/KylinConfigBase.java | 23 +- .../kylin/common/metrics/common/Metrics.java | 125 ++++++ .../common/metrics/common/MetricsConstant.java | 38 ++ .../common/metrics/common/MetricsFactory.java | 62 +++ .../kylin/common/metrics/common/MetricsScope.java | 33 ++ .../common/metrics/common/MetricsVariable.java | 27 ++ .../kylin/common/metrics/common/Metricss.java | 40 ++ .../common/metrics/metrics2/CodahaleMetrics.java | 477 +++++++++++++++++++++ .../common/metrics/metrics2/CodahaleReporter.java | 31 ++ .../metrics/metrics2/ConsoleMetricsReporter.java | 51 +++ .../metrics/metrics2/JmxMetricsReporter.java | 55 +++ .../metrics/metrics2/JsonFileMetricsReporter.java | 136 ++++++ .../metrics/metrics2/KylinObjectNameFactory.java | 68 +++ .../metrics/metrics2/MetricVariableRatioGauge.java | 47 ++ .../common/metrics/metrics2/Metrics2Reporter.java | 60 +++ .../common/metrics/metrics2/MetricsReporting.java | 26 ++ .../kylin/common/metrics/perflog/IPerfLogger.java | 48 +++ .../kylin/common/metrics/perflog/PerfLogger.java | 160 +++++++ .../common/metrics/perflog/PerfLoggerFactory.java | 56 +++ .../common/metrics/perflog/SimplePerfLogger.java | 73 ++++ kylin/pom.xml | 2 + .../kylin/rest/metrics/QueryMetrics2Facade.java | 94 ++++ .../apache/kylin/rest/service/QueryService.java | 14 +- 24 files changed, 1760 insertions(+), 3 deletions(-) create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metrics.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsConstant.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsFactory.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsScope.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsVariable.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metricss.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleMetrics.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleReporter.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/ConsoleMetricsReporter.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JmxMetricsReporter.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JsonFileMetricsReporter.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/KylinObjectNameFactory.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricVariableRatioGauge.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/Metrics2Reporter.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricsReporting.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/IPerfLogger.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLogger.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLoggerFactory.java create mode 100644 kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/SimplePerfLogger.java create mode 100644 kylin/server-base/src/main/java/org/apache/kylin/rest/metrics/QueryMetrics2Facade.java diff --git a/kylin/core-common/pom.xml b/kylin/core-common/pom.xml index 8852743c4..d58de36ed 100644 --- a/kylin/core-common/pom.xml +++ b/kylin/core-common/pom.xml @@ -68,6 +68,23 @@ junit test + + + io.dropwizard.metrics + metrics-jvm + ${dropwizard.version} + + + io.dropwizard.metrics + metrics-json + ${dropwizard.version} + + + + com.github.joshelser + dropwizard-metrics-hadoop-metrics2-reporter + 0.1.2 + diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/kylin/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java index 179d61f66..d0563e266 100644 --- a/kylin/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java @@ -1093,7 +1093,7 @@ abstract public class KylinConfigBase implements Serializable { } public boolean getQueryMetricsEnabled() { - return Boolean.parseBoolean(getOptional("kylin.server.query-metrics-enabled", "false")); + return Boolean.parseBoolean(getOptional("kylin.server.query-metrics-enabled", "true")); } public int[] getQueryMetricsPercentilesIntervals() { @@ -1120,4 +1120,25 @@ abstract public class KylinConfigBase implements Serializable { public boolean isWebCrossDomainEnabled() { return Boolean.parseBoolean(getOptional("kylin.web.cross-domain-enabled", "true")); } + + public String getPerfLoggerClassName() { + return getOptional("kylin.metric.perf-logger.class", "org.apache.kylin.common.metrics.perflog.PerfLogger"); + } + + + /** + * metric + */ + public String getCoadhaleMetricReportClassesName() { + return getOptional("kylin.metric.codahale-metric-report-classes", + "org.apache.kylin.common.metrics.metrics2.JsonFileMetricsReporter,org.apache.kylin.common.metrics.metrics2.JmxMetricsReporter"); + } + + public String getKAPMetricFileLocation() { + return getOptional("kylin.metric.file.location", "/tmp/report.json"); + } + + public Long getJsonFileMetricsReporterInterval() { + return Long.parseLong(getOptional("kylin.metric.json-file-metric-reporter.interval", "5000")); + } } diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metrics.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metrics.java new file mode 100644 index 000000000..b3d74d38a --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metrics.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.common; + +import java.util.concurrent.TimeUnit; + +/** + * Generic Metics interface. + */ +public interface Metrics { + + /** + * Deinitializes the Metrics system. + */ + public void close() throws Exception; + + /** + * + * @param name starts a scope of a given name. Scopes is stored as thread-local variable. + */ + public void startStoredScope(String name); + + /** + * Closes the stored scope of a given name. + * Note that this must be called on the same thread as where the scope was started. + * @param name + */ + public void endStoredScope(String name); + + /** + * Create scope with given name and returns it. + * @param name + * @return + */ + public MetricsScope createScope(String name); + + /** + * Close the given scope. + * @param scope + */ + public void endScope(MetricsScope scope); + + //Counter-related methods + + /** + * Increments a counter of the given name by 1. + * @param name + * @return + */ + public Long incrementCounter(String name); + + /** + * Increments a counter of the given name by "increment" + * @param name + * @param increment + * @return + */ + public Long incrementCounter(String name, long increment); + + /** + * Decrements a counter of the given name by 1. + * @param name + * @return + */ + public Long decrementCounter(String name); + + /** + * Decrements a counter of the given name by "decrement" + * @param name + * @param decrement + * @return + */ + public Long decrementCounter(String name, long decrement); + + /** + * Adds a metrics-gauge to track variable. For example, number of open database connections. + * @param name name of gauge + * @param variable variable to track. + */ + public void addGauge(String name, final MetricsVariable variable); + + /** + * Add a ratio metric to track the correlation between two variables + * @param name name of the ratio gauge + * @param numerator numerator of the ratio + * @param denominator denominator of the ratio + */ + public void addRatio(String name, MetricsVariable numerator, MetricsVariable denominator); + + /** + * Mark an event occurance for a meter. Meters measure the rate of an event and track + * 1/5/15 minute moving averages + * @param name name of the meter + */ + public void markMeter(String name); + + /** + * + * @param name name of the Timer + * @param duration + * @param unit + */ + + public void updateTimer(String name, long duration, TimeUnit unit); + + + + public void updateHistogram(String name, long count); + +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsConstant.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsConstant.java new file mode 100644 index 000000000..49c8d8e59 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsConstant.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.common; + +/** + * This class defines some metrics generated by Hive processes. + */ +public class MetricsConstant { + + public static final String API_PREFIX = "api_"; + public static final String ACTIVE_CALLS = "metrics:name=active_calls,method="; + public static final String CALLS = "metrics:name=calls,method="; + + public static final String QUERY_SUCCESS_COUNT = "QuerySuccessCount"; + public static final String QUERY_FAIL_COUNT = "QueryFailCount"; + public static final String QUERY_CACHE_COUNT = "QueryCacheCount"; + public static final String QUERY_COUNT = "QueryCount"; + public static final String QUERY_DURATION = "QueryDuration"; + public static final String QUERY_RESULT_ROWCOUNT = "QueryResultRowcount"; + public static final String QUERY_SCAN_ROWCOUNT = "QueryScanRowcount"; + public static final String TOTAL = "total"; + +} \ No newline at end of file diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsFactory.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsFactory.java new file mode 100644 index 000000000..ff0ab7db4 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsFactory.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.common; + + +import org.apache.kylin.common.metrics.metrics2.CodahaleMetrics; + +/** + * Class that manages a static Metric instance for this process. + */ +public class MetricsFactory { + + //Volatile ensures that static access returns Metrics instance in fully-initialized state. + //Alternative is to synchronize static access, which has performance penalties. + private volatile static Metrics metrics; + static { + MetricsFactory.init(); + } + + /** + * Initializes static Metrics instance. + */ + public synchronized static void init() { + if (metrics == null) { + Class metricsClass = MetricsFactory.class; + metrics = new CodahaleMetrics(); + } + } + + /** + * Returns static Metrics instance, null if not initialized or closed. + */ + public static Metrics getInstance() { + return metrics; + } + + /** + * Closes and removes static Metrics instance. + */ + public synchronized static void close() throws Exception { + if (metrics != null) { + metrics.close(); + metrics = null; + } + } +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsScope.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsScope.java new file mode 100644 index 000000000..8a720730a --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsScope.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.common; + +/** + * Metrics Scope to represent duration of an event. + * + * Implementation can capture information like the average duration of open scopes, + * number of open scopes, number of completed scopes. + * + * Scopes are created via the Metrics framework (see Metrics#createScope or Metrics$createStoredScope) + * + * Scope may be stored by the Metrics framework via 'storedScope' concept for further reference. + * + * In either case, it is the caller's responsibility to end the scope via the Metrics framework (see Metrics#endScope) + */ +public interface MetricsScope { +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsVariable.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsVariable.java new file mode 100644 index 000000000..3273c7f30 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/MetricsVariable.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.common; + +/** + * Interface for metrics variables. For example a the database service could expose the number of + * currently active connections. + */ +public interface MetricsVariable { + public T getValue(); +} \ No newline at end of file diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metricss.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metricss.java new file mode 100644 index 000000000..8465d2693 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/common/Metricss.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.common; + +public final class Metricss { + public final static String METRICS = "metrics:"; + public final static String PROJECT_TEMPLATE = METRICS + "project=%s"; + public final static String CUBE_TEMPLATE = METRICS + "project=%s,cube=%s"; + + + public static String buildMetric(String prefix, String name) { + return String.format(prefix+",name=%s", name); + } + + public static String buildCubeMetricPrefix(String project) { + return String.format(PROJECT_TEMPLATE, project); + } + + public static String buildCubeMetricPrefix(String project, String cube) { + return String.format(CUBE_TEMPLATE, project, cube); + } + + +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleMetrics.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleMetrics.java new file mode 100644 index 000000000..2e418d403 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleMetrics.java @@ -0,0 +1,477 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import java.io.Closeable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import com.codahale.metrics.Histogram; +import org.apache.commons.lang3.ClassUtils; +import org.apache.kylin.common.KylinConfig; +import org.apache.kylin.common.metrics.common.Metrics; +import org.apache.kylin.common.metrics.common.MetricsConstant; +import org.apache.kylin.common.metrics.common.MetricsScope; +import org.apache.kylin.common.metrics.common.MetricsVariable; +import org.apache.kylin.common.metrics.common.Metricss; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.ExponentiallyDecayingReservoir; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Meter; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.MetricSet; +import com.codahale.metrics.Timer; +import com.codahale.metrics.json.MetricsModule; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Lists; + + +/** + * Codahale-backed Metrics implementation. + */ +public class CodahaleMetrics implements Metrics { + + public static final Logger LOGGER = LoggerFactory.getLogger(CodahaleMetrics.class); + + public final MetricRegistry metricRegistry = new MetricRegistry(); + private final Lock timersLock = new ReentrantLock(); + private final Lock countersLock = new ReentrantLock(); + private final Lock gaugesLock = new ReentrantLock(); + private final Lock metersLock = new ReentrantLock(); + private final Lock histogramLock = new ReentrantLock(); + private final Set reporters = new HashSet(); + private final ThreadLocal> threadLocalScopes = new ThreadLocal>() { + @Override + protected HashMap initialValue() { + return new HashMap(); + } + }; + private LoadingCache timers; + private LoadingCache counters; + private LoadingCache meters; + private LoadingCache histograms; + private ConcurrentHashMap gauges; + private KylinConfig conf; + + public CodahaleMetrics() { + this.conf = conf; + //Codahale artifacts are lazily-created. + timers = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public Timer load(String key) { + Timer timer = new Timer(new ExponentiallyDecayingReservoir()); + metricRegistry.register(key, timer); + return timer; + } + }); + counters = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public Counter load(String key) { + Counter counter = new Counter(); + metricRegistry.register(key, counter); + return counter; + } + }); + meters = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public Meter load(String key) { + Meter meter = new Meter(); + metricRegistry.register(key, meter); + return meter; + } + }); + histograms = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public Histogram load(String key) { + Histogram histogram = new Histogram(new ExponentiallyDecayingReservoir()); + metricRegistry.register(key, histogram); + return histogram; + } + }); + gauges = new ConcurrentHashMap(); + //register JVM metrics +// registerAll("gc", new GarbageCollectorMetricSet()); +// registerAll("buffers", new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer())); +// registerAll("memory", new MemoryUsageGaugeSet()); +// registerAll("threads", new ThreadStatesGaugeSet()); +// registerAll("classLoadingz", new ClassLoadingGaugeSet()); + + //initialize reporters + initReporting(); + } + + @Override + public void close() throws Exception { + if (reporters != null) { + for (Closeable reporter : reporters) { + reporter.close(); + } + } + for (Map.Entry metric : metricRegistry.getMetrics().entrySet()) { + metricRegistry.remove(metric.getKey()); + } + timers.invalidateAll(); + counters.invalidateAll(); + meters.invalidateAll(); + } + + @Override + public void startStoredScope(String name) { + if (threadLocalScopes.get().containsKey(name)) { + threadLocalScopes.get().get(name).open(); + } else { + threadLocalScopes.get().put(name, new CodahaleMetricsScope(name)); + } + } + + public MetricsScope getStoredScope(String name) throws IllegalArgumentException { + if (threadLocalScopes.get().containsKey(name)) { + return threadLocalScopes.get().get(name); + } else { + throw new IllegalArgumentException("No metrics scope named " + name); + } + } + + @Override + public void endStoredScope(String name) { + if (threadLocalScopes.get().containsKey(name)) { + threadLocalScopes.get().get(name).close(); + threadLocalScopes.get().remove(name); + } + } + + public MetricsScope createScope(String name) { + return new CodahaleMetricsScope(name); + } + + public void endScope(MetricsScope scope) { + ((CodahaleMetricsScope) scope).close(); + } + + @Override + public Long incrementCounter(String name) { + return incrementCounter(name, 1L); + } + + @Override + public Long incrementCounter(String name, long increment) { + String key = name; + try { + countersLock.lock(); + counters.get(key).inc(increment); + return counters.get(key).getCount(); + } catch (ExecutionException ee) { + throw new IllegalStateException("Error retrieving counter from the metric registry ", ee); + } finally { + countersLock.unlock(); + } + } + + @Override + public Long decrementCounter(String name) { + return decrementCounter(name, 1L); + } + + @Override + public Long decrementCounter(String name, long decrement) { + String key = name; + try { + countersLock.lock(); + counters.get(key).dec(decrement); + return counters.get(key).getCount(); + } catch (ExecutionException ee) { + throw new IllegalStateException("Error retrieving counter from the metric registry ", ee); + } finally { + countersLock.unlock(); + } + } + + @Override + public void addGauge(String name, final MetricsVariable variable) { + Gauge gauge = new Gauge() { + @Override + public Object getValue() { + return variable.getValue(); + } + }; + addGaugeInternal(name, gauge); + } + + @Override + public void addRatio(String name, MetricsVariable numerator, MetricsVariable denominator) { + Preconditions.checkArgument(numerator != null, "Numerator must not be null"); + Preconditions.checkArgument(denominator != null, "Denominator must not be null"); + + MetricVariableRatioGauge gauge = new MetricVariableRatioGauge(numerator, denominator); + addGaugeInternal(name, gauge); + } + + private void addGaugeInternal(String name, Gauge gauge) { + try { + gaugesLock.lock(); + gauges.put(name, gauge); + // Metrics throws an Exception if we don't do this when the key already exists + if (metricRegistry.getGauges().containsKey(name)) { + LOGGER.warn("A Gauge with name [" + name + "] already exists. " + + " The old gauge will be overwritten, but this is not recommended"); + metricRegistry.remove(name); + } + metricRegistry.register(name, gauge); + } finally { + gaugesLock.unlock(); + } + } + + @Override + public void markMeter(String name) { + String key = name; + try { + metersLock.lock(); + Meter meter = meters.get(name); + meter.mark(); + } catch (ExecutionException e) { + throw new IllegalStateException("Error retrieving meter " + name + " from the metric registry ", e); + } finally { + metersLock.unlock(); + } + } + + @Override + public void updateHistogram(String name, long count){ + try { + histogramLock.lock(); + Histogram histogram = histograms.get(name); + histogram.update(count); + } catch (ExecutionException e) { + throw new IllegalStateException("Error retrieving meter " + name + " from the metric registry ", e); + } finally { + histogramLock.unlock(); + } + } + + @Override + public void updateTimer(String name, long duration, TimeUnit unit){ + String key = name; + try { + timersLock.lock(); + Timer timer = timers.get(key); + timer.update(duration, unit); + } catch (ExecutionException e) { + throw new IllegalStateException("Error retrieving timer " + name + " from the metric registry ", e); + } finally { + timersLock.unlock(); + } + } + // This method is necessary to synchronize lazy-creation to the timers. + private Timer getTimer(String name) { + try { + timersLock.lock(); + return timers.get(name); + } catch (ExecutionException e) { + throw new IllegalStateException("Error retrieving timer " + name + " from the metric registry ", e); + } finally { + timersLock.unlock(); + } + } + + + private void registerAll(String prefix, MetricSet metricSet) { + for (Map.Entry entry : metricSet.getMetrics().entrySet()) { + if (entry.getValue() instanceof MetricSet) { + registerAll(prefix + "." + entry.getKey(), (MetricSet) entry.getValue()); + } else { + metricRegistry.register(prefix + "." + entry.getKey(), entry.getValue()); + } + } + } + + @VisibleForTesting + public MetricRegistry getMetricRegistry() { + return metricRegistry; + } + + @VisibleForTesting + public String dumpJson() throws Exception { + ObjectMapper jsonMapper = new ObjectMapper() + .registerModule(new MetricsModule(TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS, false)); + return jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(metricRegistry); + } + + /** + * Initializes reporters from HIVE_CODAHALE_METRICS_REPORTER_CLASSES or HIVE_METRICS_REPORTER if the former is not defined. + * Note: if both confs are defined, only HIVE_CODAHALE_METRICS_REPORTER_CLASSES will be used. + */ + private void initReporting() { + + if (!(initCodahaleMetricsReporterClasses() || initMetricsReporter())) { + LOGGER.warn("Unable to initialize metrics reporting"); + } + if (reporters.isEmpty()) { + // log a warning incase no reporters were successfully added + LOGGER.warn("No reporters configured for codahale metrics!"); + } + } + + /** + * Initializes reporting using HIVE_CODAHALE_METRICS_REPORTER_CLASSES. + * @return whether initialization was successful or not + */ + private boolean initCodahaleMetricsReporterClasses() { + + List reporterClasses = Lists.newArrayList(Splitter.on(",").trimResults().omitEmptyStrings() + .split(KylinConfig.getInstanceFromEnv().getCoadhaleMetricReportClassesName())); + if (reporterClasses.isEmpty()) { + return false; + } + + for (String reporterClass : reporterClasses) { + Class name = null; + try { + name = ClassUtils.getClass(reporterClass); + } catch (ClassNotFoundException e) { + LOGGER.error("Unable to instantiate metrics reporter class " + reporterClass + + " from conf HIVE_CODAHALE_METRICS_REPORTER_CLASSES", e); + throw new IllegalArgumentException(e); + } + try { + Constructor constructor = name.getConstructor(MetricRegistry.class, KylinConfig.class); + CodahaleReporter reporter = (CodahaleReporter) constructor.newInstance(metricRegistry, conf); + reporter.start(); + reporters.add(reporter); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException + | InvocationTargetException e) { + LOGGER.error("Unable to instantiate using constructor(MetricRegistry, HiveConf) for" + " reporter " + + reporterClass + " from conf HIVE_CODAHALE_METRICS_REPORTER_CLASSES", e); + throw new IllegalArgumentException(e); + } + } + return true; + } + + /** + * Initializes reporting using HIVE_METRICS+REPORTER. + * @return whether initialization was successful or not + */ + private boolean initMetricsReporter() { + + List metricsReporterNames = Lists.newArrayList(Splitter.on(",").trimResults().omitEmptyStrings() + .split(KylinConfig.getInstanceFromEnv().getCoadhaleMetricReportClassesName())); + if (metricsReporterNames.isEmpty()) { + return false; + } + + MetricsReporting reporter = null; + for (String metricsReportingName : metricsReporterNames) { + try { + reporter = MetricsReporting.valueOf(metricsReportingName.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + LOGGER.error("Invalid reporter name " + metricsReportingName, e); + throw e; + } + CodahaleReporter codahaleReporter = null; + switch (reporter) { + case CONSOLE: + codahaleReporter = new ConsoleMetricsReporter(metricRegistry, conf); + break; + case JMX: + codahaleReporter = new JmxMetricsReporter(metricRegistry, conf); + break; + case JSON_FILE: + codahaleReporter = new JsonFileMetricsReporter(metricRegistry, conf); + break; + case HADOOP2: + codahaleReporter = new Metrics2Reporter(metricRegistry, conf); + break; + default: + LOGGER.warn("Unhandled reporter " + reporter + " provided."); + } + if (codahaleReporter != null) { + codahaleReporter.start(); + reporters.add(codahaleReporter); + } + } + return true; + } + + public class CodahaleMetricsScope implements MetricsScope { + + private final String name; + private final Timer timer; + private Timer.Context timerContext; + + private boolean isOpen = false; + + /** + * Instantiates a named scope - intended to only be called by Metrics, so locally scoped. + * @param name - name of the variable + */ + private CodahaleMetricsScope(String name) { + this.name = name; + this.timer = CodahaleMetrics.this.getTimer(MetricsConstant.CALLS + name); + open(); + } + + /** + * Opens scope, and makes note of the time started, increments run counter + * + */ + public void open() { + if (!isOpen) { + isOpen = true; + this.timerContext = timer.time(); + CodahaleMetrics.this.incrementCounter(MetricsConstant.ACTIVE_CALLS + name); + } else { + LOGGER.warn("Scope named " + name + " is not closed, cannot be opened."); + } + } + + /** + * Closes scope, and records the time taken + */ + public void close() { + if (isOpen) { + timerContext.close(); + CodahaleMetrics.this.decrementCounter(MetricsConstant.ACTIVE_CALLS + name); + } else { + LOGGER.warn("Scope named " + name + " is not open, cannot be closed."); + } + isOpen = false; + } + } +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleReporter.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleReporter.java new file mode 100644 index 000000000..354f42777 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/CodahaleReporter.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import java.io.Closeable; + +import com.codahale.metrics.Reporter; + +public interface CodahaleReporter extends Closeable, Reporter { + + /** + * Start the reporter. + */ + public void start(); +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/ConsoleMetricsReporter.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/ConsoleMetricsReporter.java new file mode 100644 index 000000000..b292443ea --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/ConsoleMetricsReporter.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import java.util.concurrent.TimeUnit; + +import org.apache.kylin.common.KylinConfig; + +import com.codahale.metrics.ConsoleReporter; +import com.codahale.metrics.MetricRegistry; + +/** + * A wrapper around Codahale ConsoleReporter to make it a pluggable/configurable Hive Metrics reporter. + */ +public class ConsoleMetricsReporter implements CodahaleReporter { + + private final ConsoleReporter reporter; + + public ConsoleMetricsReporter(MetricRegistry registry, KylinConfig conf) { + + reporter = ConsoleReporter.forRegistry(registry).convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS).build(); + + } + + @Override + public void start() { + reporter.start(10, TimeUnit.SECONDS); + } + + @Override + public void close() { + reporter.close(); + } +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JmxMetricsReporter.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JmxMetricsReporter.java new file mode 100644 index 000000000..2b18f2ea6 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JmxMetricsReporter.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import java.util.concurrent.TimeUnit; + +import org.apache.kylin.common.KylinConfig; + +import com.codahale.metrics.JmxReporter; +import com.codahale.metrics.MetricRegistry; + +/** + * A wrapper around Codahale JmxReporter to make it a pluggable/configurable Hive Metrics reporter. + */ +public class JmxMetricsReporter implements CodahaleReporter { + + private final MetricRegistry registry; + private final KylinConfig conf; + private final JmxReporter jmxReporter; + + public JmxMetricsReporter(MetricRegistry registry, KylinConfig conf) { + this.registry = registry; + this.conf = conf; + + jmxReporter = JmxReporter.forRegistry(registry).convertRatesTo(TimeUnit.SECONDS) + .createsObjectNamesWith(new KylinObjectNameFactory()).convertDurationsTo(TimeUnit.MILLISECONDS).build(); + } + + @Override + public void start() { + jmxReporter.start(); + } + + @Override + public void close() { + jmxReporter.close(); + } + +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JsonFileMetricsReporter.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JsonFileMetricsReporter.java new file mode 100644 index 000000000..123635aca --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/JsonFileMetricsReporter.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URI; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.kylin.common.KylinConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.json.MetricsModule; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +/** + * A metrics reporter for CodahaleMetrics that dumps metrics periodically into a file in JSON format. + */ + +public class JsonFileMetricsReporter implements CodahaleReporter { + + private static final Logger LOGGER = LoggerFactory.getLogger(JsonFileMetricsReporter.class); + private final MetricRegistry metricRegistry; + private final ObjectWriter jsonWriter; + private final ScheduledExecutorService executorService; + private final KylinConfig conf; + private final long interval; + private final String pathString; + private final Path path; + + public JsonFileMetricsReporter(MetricRegistry registry, KylinConfig conf) { + this.metricRegistry = registry; + this.jsonWriter = new ObjectMapper() + .registerModule(new MetricsModule(TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS, false)) + .writerWithDefaultPrettyPrinter(); + executorService = Executors.newSingleThreadScheduledExecutor(); + this.conf = conf; + + interval = KylinConfig.getInstanceFromEnv().getJsonFileMetricsReporterInterval(); + pathString = KylinConfig.getInstanceFromEnv().getKAPMetricFileLocation(); + path = new Path(pathString); + } + + @Override + public void start() { + + final Path tmpPath = new Path(pathString + ".tmp"); + URI tmpPathURI = tmpPath.toUri(); + final FileSystem fs; + try { + if (tmpPathURI.getScheme() == null && tmpPathURI.getAuthority() == null) { + //default local + fs = FileSystem.getLocal(new Configuration()); + } else { + fs = FileSystem.get(tmpPathURI, new Configuration()); + } + } catch (IOException e) { + LOGGER.error("Unable to access filesystem for path " + tmpPath + ". Aborting reporting", e); + return; + } + + Runnable task = new Runnable() { + public void run() { + try { + String json = null; + try { + json = jsonWriter.writeValueAsString(metricRegistry); + } catch (JsonProcessingException e) { + LOGGER.error("Unable to convert json to string ", e); + return; + } + + BufferedWriter bw = null; + try { + fs.delete(tmpPath, true); + bw = new BufferedWriter(new OutputStreamWriter(fs.create(tmpPath, true))); + bw.write(json); + fs.setPermission(tmpPath, FsPermission.createImmutable((short) 0644)); + } catch (IOException e) { + LOGGER.error("Unable to write to temp file " + tmpPath, e); + return; + } finally { + if (bw != null) { + bw.close(); + } + } + + try { + fs.rename(tmpPath, path); + fs.setPermission(path, FsPermission.createImmutable((short) 0644)); + } catch (IOException e) { + LOGGER.error("Unable to rename temp file " + tmpPath + " to " + pathString, e); + return; + } + } catch (Throwable t) { + // catch all errors (throwable and execptions to prevent subsequent tasks from being suppressed) + LOGGER.error("Error executing scheduled task ", t); + } + } + }; + + executorService.scheduleWithFixedDelay(task, 0, interval, TimeUnit.MILLISECONDS); + } + + @Override + public void close() { + executorService.shutdown(); + } +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/KylinObjectNameFactory.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/KylinObjectNameFactory.java new file mode 100644 index 000000000..fe6fad7c7 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/KylinObjectNameFactory.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import java.util.Hashtable; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.codahale.metrics.ObjectNameFactory; + +public class KylinObjectNameFactory implements ObjectNameFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(KylinObjectNameFactory.class); + + @Override + public ObjectName createName(String type, String domain, String name) { + try { + if (name.startsWith(domain)) { + ObjectName objectName = new ObjectName(name); + return objectName; + } + + ObjectName objectName = new ObjectName(domain, "name", name); + if (objectName.isPattern()) { + objectName = new ObjectName(domain, "name", ObjectName.quote(name)); + } + return objectName; + } catch (MalformedObjectNameException e) { + try { + return new ObjectName(domain, "name", ObjectName.quote(name)); + } catch (MalformedObjectNameException e1) { + LOGGER.warn("Unable to register {} {}", type, name, e1); + throw new RuntimeException(e1); + } + } + } + + public ObjectName process(String domain, String name) throws MalformedObjectNameException { + String[] kvArry = name.split(","); + Hashtable hashTable = new Hashtable<>(); + for (int i = 0; i < kvArry.length; i++) { + String[] split = kvArry[i].split("="); + hashTable.put(split[0],split[1]); + } + ObjectName objectName = new ObjectName(domain, hashTable); + return objectName; + } +} \ No newline at end of file diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricVariableRatioGauge.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricVariableRatioGauge.java new file mode 100644 index 000000000..b49093d38 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricVariableRatioGauge.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import com.codahale.metrics.RatioGauge; +import org.apache.kylin.common.metrics.common.MetricsVariable; + + +/** + * Combines two numeric metric variables into one gauge type metric displaying their ratio + */ +public class MetricVariableRatioGauge extends RatioGauge { + + private final MetricsVariable numerator; + private final MetricsVariable denominator; + + public MetricVariableRatioGauge(MetricsVariable numerator, MetricsVariable denominator) { + this.numerator = numerator; + this.denominator = denominator; + } + + @Override + protected Ratio getRatio() { + Integer numValue = numerator.getValue(); + Integer denomValue = denominator.getValue(); + if (numValue != null && denomValue != null) { + return Ratio.of(numValue.doubleValue(), denomValue.doubleValue()); + } + return Ratio.of(0d, 0d); + } +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/Metrics2Reporter.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/Metrics2Reporter.java new file mode 100644 index 000000000..d1c3c7fe0 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/Metrics2Reporter.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +import java.util.concurrent.TimeUnit; + +import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem; +import org.apache.kylin.common.KylinConfig; + +import com.codahale.metrics.MetricRegistry; +import com.github.joshelser.dropwizard.metrics.hadoop.HadoopMetrics2Reporter; + +/** + * A wrapper around Codahale HadoopMetrics2Reporter to make it a pluggable/configurable Hive Metrics reporter. + */ +public class Metrics2Reporter implements CodahaleReporter { + + private final MetricRegistry metricRegistry; + private final KylinConfig conf; + private final HadoopMetrics2Reporter reporter; + + public Metrics2Reporter(MetricRegistry registry, KylinConfig conf) { + this.metricRegistry = registry; + this.conf = conf; + String applicationName = "kylin"; + + reporter = HadoopMetrics2Reporter.forRegistry(metricRegistry).convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS).build(DefaultMetricsSystem.initialize(applicationName), // The application-level name + applicationName, // Component name + applicationName, // Component description + "General"); // Name for each metric record + } + + @Override + public void start() { + long reportingInterval = 30; + reporter.start(reportingInterval, TimeUnit.SECONDS); + } + + @Override + public void close() { + reporter.close(); + } +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricsReporting.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricsReporting.java new file mode 100644 index 000000000..fc1b66364 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/metrics2/MetricsReporting.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.metrics2; + +/** + * Reporting options for org.apache.hadoop.hive.common.metrics.metrics2.Metrics. + */ +public enum MetricsReporting { + JMX, CONSOLE, JSON_FILE, HADOOP2 +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/IPerfLogger.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/IPerfLogger.java new file mode 100644 index 000000000..83a421009 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/IPerfLogger.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.perflog; + +public interface IPerfLogger { + + /** + * Call this function when you start to measure time spent by a piece of code. + * + * @param callerName the logging object to be used. + * @param method method or ID that identifies this perf log element. + */ + public void perfLogBegin(String callerName, String method); + + /** + * Call this function in correspondence of perfLogBegin to mark the end of the measurement. + * + * @param callerName + * @param method + * @return long duration the difference between now and startTime, or -1 if startTime is null + */ + public long perfLogEnd(String callerName, String method); + + /** + * Call this function in correspondence of perfLogBegin to mark the end of the measurement. + * + * @param callerName + * @param method + * @return long duration the difference between now and startTime, or -1 if startTime is null + */ + public long perfLogEnd(String callerName, String method, String additionalInfo); +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLogger.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLogger.java new file mode 100644 index 000000000..361a65428 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLogger.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.perflog; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.kylin.common.metrics.common.Metrics; +import org.apache.kylin.common.metrics.common.MetricsConstant; +import org.apache.kylin.common.metrics.common.MetricsFactory; +import org.apache.kylin.common.metrics.common.MetricsScope; +import org.apache.kylin.common.metrics.common.Metricss; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; + + +/** + * PerfLogger. + *

+ * Can be used to measure and log the time spent by a piece of code. + */ +public class PerfLogger implements IPerfLogger { + + static final private Logger LOG = LoggerFactory.getLogger(PerfLogger.class.getName()); + protected final Map startTimes = new HashMap(); + protected final Map endTimes = new HashMap(); + //Methods for metrics integration. Each thread-local PerfLogger will open/close scope during each perf-log method. + transient Map openScopes = new HashMap(); + + public void perfLogBegin(String callerName, String method) { + long startTime = System.currentTimeMillis(); + startTimes.put(method, new Long(startTime)); + if (LOG.isDebugEnabled()) { + LOG.debug(""); + } + beginMetrics(callerName + "." + method); + } + + public long perfLogEnd(String callerName, String method) { + return perfLogEnd(callerName, method, null); + } + + public long perfLogEnd(String callerName, String method, String additionalInfo) { + Long startTime = startTimes.get(method); + long endTime = System.currentTimeMillis(); + endTimes.put(method, new Long(endTime)); + long duration = startTime == null ? -1 : endTime - startTime.longValue(); + + if (LOG.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(""); + LOG.debug(sb.toString()); + } + endMetrics(callerName + "." + method); + return duration; + } + + public Long getStartTime(String method) { + long startTime = 0L; + + if (startTimes.containsKey(method)) { + startTime = startTimes.get(method); + } + return startTime; + } + + public Long getEndTime(String method) { + long endTime = 0L; + + if (endTimes.containsKey(method)) { + endTime = endTimes.get(method); + } + return endTime; + } + + public boolean startTimeHasMethod(String method) { + return startTimes.containsKey(method); + } + + public boolean endTimeHasMethod(String method) { + return endTimes.containsKey(method); + } + + public Long getDuration(String method) { + long duration = 0; + if (startTimes.containsKey(method) && endTimes.containsKey(method)) { + duration = endTimes.get(method) - startTimes.get(method); + } + return duration; + } + + public ImmutableMap getStartTimes() { + return ImmutableMap.copyOf(startTimes); + } + + public ImmutableMap getEndTimes() { + return ImmutableMap.copyOf(endTimes); + } + + private void beginMetrics(String method) { + Metrics metrics = MetricsFactory.getInstance(); + if (metrics != null) { + MetricsScope scope = metrics.createScope(method); + openScopes.put(method, scope); + } + + } + + private void endMetrics(String method) { + Metrics metrics = MetricsFactory.getInstance(); + if (metrics != null) { + MetricsScope scope = openScopes.remove(method); + if (scope != null) { + metrics.endScope(scope); + } + } + } + + /** + * Cleans up any dangling perfLog metric call scopes. + */ + public void cleanupPerfLogMetrics() { + Metrics metrics = MetricsFactory.getInstance(); + if (metrics != null) { + for (MetricsScope openScope : openScopes.values()) { + metrics.endScope(openScope); + } + } + openScopes.clear(); + } +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLoggerFactory.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLoggerFactory.java new file mode 100644 index 000000000..c76aece81 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/PerfLoggerFactory.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.perflog; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.kylin.common.KylinConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PerfLoggerFactory { + + protected static final ThreadLocal perfLogger = new ThreadLocal(); + static final private Logger LOG = LoggerFactory.getLogger(PerfLoggerFactory.class.getName()); + + public static IPerfLogger getPerfLogger() { + return getPerfLogger(false); + } + + public static void setPerfLogger(IPerfLogger resetKAPPerfLogger) { + perfLogger.set(resetKAPPerfLogger); + } + + public static IPerfLogger getPerfLogger(boolean resetPerfLogger) { + IPerfLogger result = perfLogger.get(); + if (resetPerfLogger || result == null) { + try { + result = (IPerfLogger) ClassUtils.getClass(KylinConfig.getInstanceFromEnv().getPerfLoggerClassName()) + .newInstance(); + } catch (ClassNotFoundException e) { + LOG.error("Performance Logger Class not found:" + e.getMessage()); + result = new SimplePerfLogger(); + } catch (IllegalAccessException | InstantiationException e) { + e.printStackTrace(); + } + perfLogger.set(result); + } + return result; + } + +} diff --git a/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/SimplePerfLogger.java b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/SimplePerfLogger.java new file mode 100644 index 000000000..cd88c6548 --- /dev/null +++ b/kylin/core-common/src/main/java/org/apache/kylin/common/metrics/perflog/SimplePerfLogger.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.common.metrics.perflog; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SimplePerfLogger implements IPerfLogger { + + static final private Logger LOG = LoggerFactory.getLogger(SimplePerfLogger.class.getName()); + protected final Map startTimes = new HashMap(); + protected final Map endTimes = new HashMap(); + + protected SimplePerfLogger() { + } + + public void perfLogBegin(String callerName, String method) { + long startTime = System.currentTimeMillis(); + startTimes.put(method, new Long(startTime)); + if (LOG.isDebugEnabled()) { + LOG.debug(""); + } + } + + public long perfLogEnd(String callerName, String method) { + return perfLogEnd(callerName, method, null); + } + + public long perfLogEnd(String callerName, String method, String additionalInfo) { + Long startTime = startTimes.get(method); + long endTime = System.currentTimeMillis(); + endTimes.put(method, new Long(endTime)); + long duration = startTime == null ? -1 : endTime - startTime.longValue(); + + if (LOG.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(""); + LOG.debug(sb.toString()); + } + return duration; + } + +} diff --git a/kylin/pom.xml b/kylin/pom.xml index 732deac41..ff4a9ebf6 100644 --- a/kylin/pom.xml +++ b/kylin/pom.xml @@ -122,6 +122,8 @@ 2.6.4 1.8.9 + 3.1.2 + jacoco reuseReports diff --git a/kylin/server-base/src/main/java/org/apache/kylin/rest/metrics/QueryMetrics2Facade.java b/kylin/server-base/src/main/java/org/apache/kylin/rest/metrics/QueryMetrics2Facade.java new file mode 100644 index 000000000..afbf4a64b --- /dev/null +++ b/kylin/server-base/src/main/java/org/apache/kylin/rest/metrics/QueryMetrics2Facade.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.rest.metrics; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hadoop.metrics2.MetricsException; +import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem; +import org.apache.kylin.common.KylinConfig; +import org.apache.kylin.common.metrics.common.Metrics; +import org.apache.kylin.common.metrics.common.MetricsConstant; +import org.apache.kylin.common.metrics.common.MetricsFactory; +import org.apache.kylin.common.metrics.common.Metricss; +import org.apache.kylin.rest.request.SQLRequest; +import org.apache.kylin.rest.response.SQLResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.kylin.common.metrics.common.MetricsConstant.TOTAL; +import static org.apache.kylin.common.metrics.common.Metricss.buildCubeMetricPrefix; + +/** + * The entrance of metrics features. + */ +@ThreadSafe +public class QueryMetrics2Facade { + + private static final Logger logger = LoggerFactory.getLogger(QueryMetrics2Facade.class); + private static Metrics metrics ; + + public static void updateMetrics(SQLRequest sqlRequest, SQLResponse sqlResponse) { + if(metrics == null){ + metrics = MetricsFactory.getInstance(); + } + String projectName = sqlRequest.getProject(); + String cubeName = sqlResponse.getCube().replace("=","->"); + +// update(getQueryMetrics("Server_Total"), sqlResponse); + update(buildCubeMetricPrefix(TOTAL),sqlResponse); + update(buildCubeMetricPrefix(projectName), sqlResponse); + String cubeMetricName =buildCubeMetricPrefix(projectName,cubeName); + update(cubeMetricName, sqlResponse); + } + + private static void update(String name, SQLResponse sqlResponse) { + try { + incrQueryCount(name, sqlResponse); + incrCacheHitCount(name, sqlResponse); + if (!sqlResponse.getIsException()) { + metrics.updateTimer(Metricss.buildMetric(name, MetricsConstant.QUERY_DURATION), sqlResponse.getDuration(),TimeUnit.MILLISECONDS); + metrics.updateHistogram(Metricss.buildMetric(name, MetricsConstant.QUERY_RESULT_ROWCOUNT), sqlResponse.getResults().size()); + metrics.updateHistogram(Metricss.buildMetric(name, MetricsConstant.QUERY_SCAN_ROWCOUNT), sqlResponse.getTotalScanCount()); + } + } catch (Exception e) { + logger.error(e.getMessage()); + } + + } + + private static void incrQueryCount(String name, SQLResponse sqlResponse) { + if (!sqlResponse.isHitExceptionCache() && !sqlResponse.getIsException()) { + metrics.incrementCounter(Metricss.buildMetric(name, MetricsConstant.QUERY_SUCCESS_COUNT)); + } else { + metrics.incrementCounter(Metricss.buildMetric(name, MetricsConstant.QUERY_FAIL_COUNT)); + } + metrics.incrementCounter(Metricss.buildMetric(name, MetricsConstant.QUERY_COUNT)); + } + + private static void incrCacheHitCount(String name, SQLResponse sqlResponse) { + if (sqlResponse.isStorageCacheUsed()) { + metrics.incrementCounter(Metricss.buildMetric(name, MetricsConstant.QUERY_CACHE_COUNT)); + } + } + +} diff --git a/kylin/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java b/kylin/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java index f4ae06ca1..6e9e4b3f6 100644 --- a/kylin/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java +++ b/kylin/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java @@ -58,6 +58,8 @@ import org.apache.kylin.common.KylinConfig; import org.apache.kylin.common.QueryContext; import org.apache.kylin.common.debug.BackdoorToggles; import org.apache.kylin.common.exceptions.ResourceLimitExceededException; +import org.apache.kylin.common.metrics.perflog.IPerfLogger; +import org.apache.kylin.common.metrics.perflog.PerfLoggerFactory; import org.apache.kylin.common.persistence.ResourceStore; import org.apache.kylin.common.persistence.RootPersistentEntity; import org.apache.kylin.common.persistence.Serializer; @@ -86,6 +88,7 @@ import org.apache.kylin.query.util.QueryUtil; import org.apache.kylin.rest.constant.Constant; import org.apache.kylin.rest.exception.BadRequestException; import org.apache.kylin.rest.exception.InternalErrorException; +import org.apache.kylin.rest.metrics.QueryMetrics2Facade; import org.apache.kylin.rest.metrics.QueryMetricsFacade; import org.apache.kylin.rest.model.Query; import org.apache.kylin.rest.msg.Message; @@ -129,6 +132,7 @@ public class QueryService extends BasicService { private static final Logger logger = LoggerFactory.getLogger(QueryService.class); final BadQueryDetector badQueryDetector = new BadQueryDetector(); final ResourceStore queryStore; + public static final String CLASS_NAME = QueryService.class.getCanonicalName(); @Autowired protected CacheManager cacheManager; @@ -170,6 +174,8 @@ public class QueryService extends BasicService { } public SQLResponse query(SQLRequest sqlRequest) throws Exception { + IPerfLogger perfLogger = PerfLoggerFactory.getPerfLogger(); + perfLogger.perfLogBegin(CLASS_NAME, "query"); try { final String user = SecurityContextHolder.getContext().getAuthentication().getName(); badQueryDetector.queryStart(Thread.currentThread(), sqlRequest, user); @@ -178,6 +184,8 @@ public class QueryService extends BasicService { } finally { badQueryDetector.queryEnd(Thread.currentThread()); + perfLogger.perfLogEnd(QueryService.class.getName(), "query"); + } } @@ -434,6 +442,7 @@ public class QueryService extends BasicService { logQuery(sqlRequest, sqlResponse); + QueryMetrics2Facade.updateMetrics(sqlRequest, sqlResponse); QueryMetricsFacade.updateMetrics(sqlRequest, sqlResponse); if (sqlResponse.getIsException()) @@ -504,7 +513,9 @@ public class QueryService extends BasicService { // force clear the query context before a new query OLAPContext.clearThreadLocalContexts(); - return execute(correctedSql, sqlRequest); + SQLResponse sqlResponse = execute(correctedSql, sqlRequest); + + return sqlResponse; } @@ -751,7 +762,6 @@ public class QueryService extends BasicService { Statement stat = null; ResultSet resultSet = null; Boolean isPushDown = false; - List> results = Lists.newArrayList(); List columnMetas = Lists.newArrayList(); -- 2.13.2 (Apple Git-90)