/* * 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 gobblin.metrics.example; import java.io.IOException; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.Counter; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.Timer; import gobblin.metrics.event.JobEvent; import gobblin.metrics.reporter.ContextAwareScheduledReporter; import gobblin.metrics.MetricContext; import gobblin.metrics.Tag; /** * A base class for those that exemplifies the usage of {@link MetricContext} and a given * {@link ContextAwareScheduledReporter}. * * <p> * This class simulating a record processing job that spawns a configurable number of tasks * running in a thread pool for processing incoming data records in parallel. Each task will * simulate processing the same configurable number of records. The job creates a job-level * {@link MetricContext}, from which a child {@link MetricContext} for each task is created. * Each task uses four metrics in its {@link MetricContext} to keep track of the total number * of records processed, record processing rate, record size distribution, and record processing * times. Since the job-level {@link MetricContext} is the parent of the {@link MetricContext}s * of the tasks, updates to a metric of a task will be automatically applied to the metric with * the same name in the job-level {@link MetricContext}. The job and task {@link MetricContext}s * use the same given {@link ContextAwareScheduledReporter} to report metrics. * </p> * * @author Yinan Li */ public class ReporterExampleBase { private static final Logger LOGGER = LoggerFactory.getLogger(ReporterExampleBase.class); private static final String JOB_NAME = "ExampleJob"; private static final String TASK_ID_KEY = "task.id"; private static final String TASK_ID_PREFIX = "ExampleTask_"; private static final String TOTAL_RECORDS = "totalRecords"; private static final String RECORD_PROCESS_RATE = "recordProcessRate"; private static final String RECORD_PROCESS_TIME = "recordProcessTime"; private static final String RECORD_SIZES = "recordSizes"; private final ExecutorService executor; private final MetricContext context; private final ContextAwareScheduledReporter.Builder reporterBuilder; private final int tasks; private final long totalRecords; public ReporterExampleBase(ContextAwareScheduledReporter.Builder reporterBuilder, int tasks, long totalRecords) { this.executor = Executors.newFixedThreadPool(10); this.context = MetricContext.builder("Job") .addTag(new Tag<String>(JobEvent.METADATA_JOB_NAME, "ExampleJob")) .addTag(new Tag<String>(JobEvent.METADATA_JOB_ID, JOB_NAME + "_" + System.currentTimeMillis())) .build(); this.reporterBuilder = reporterBuilder; this.tasks = tasks; this.totalRecords = totalRecords; } /** * Run the example. */ public void run() throws Exception { try { CountDownLatch countDownLatch = new CountDownLatch(this.tasks); for (int i = 0; i < this.tasks; i++) { addTask(i, countDownLatch); } // Wait for the tasks to finish countDownLatch.await(); } finally { try { // Calling close() will stop metric reporting this.context.close(); } finally { this.executor.shutdownNow(); } } } private void addTask(int taskIndex, CountDownLatch countDownLatch) { // Build the context of this task, which is a child of the job's context. // Tags of the job (parent) context will be inherited automatically. MetricContext taskContext = this.context.childBuilder("Task" + taskIndex) .addTag(new Tag<String>(TASK_ID_KEY, TASK_ID_PREFIX + taskIndex)) .build(); Task task = new Task(taskContext, taskIndex, this.totalRecords, countDownLatch); this.executor.execute(task); } private static class Task implements Runnable { private final MetricContext context; private final int taskIndex; private final long totalRecords; private final CountDownLatch countDownLatch; private final Random rand = new Random(); public Task(MetricContext context, int taskIndex, long totalRecords, CountDownLatch countDownLatch) { this.context = context; this.taskIndex = taskIndex; this.totalRecords = totalRecords; this.countDownLatch = countDownLatch; } @Override public void run() { Counter totalRecordsCounter = this.context.contextAwareCounter(TOTAL_RECORDS); Meter recordProcessRateMeter = this.context.contextAwareMeter(RECORD_PROCESS_RATE); Timer recordProcessTimeTimer = this.context.contextAwareTimer(RECORD_PROCESS_TIME); Histogram recordSizesHistogram = this.context.contextAwareHistogram(RECORD_SIZES); try { for (int i = 0; i < this.totalRecords; i++) { totalRecordsCounter.inc(); recordProcessRateMeter.mark(); recordSizesHistogram.update((this.rand.nextLong() & Long.MAX_VALUE) % 5000l); if (i % 100 == 0) { LOGGER.info(String.format("Task %d has processed %d records so far", this.taskIndex, i)); } long processTime = (this.rand.nextLong() & Long.MAX_VALUE) % 10; // Simulate record processing by sleeping for a random amount of time try { Thread.sleep(processTime); } catch (InterruptedException ie) { LOGGER.warn(String.format("Task %d has been interrupted", this.taskIndex)); Thread.currentThread().interrupt(); return; } recordProcessTimeTimer.update(processTime, TimeUnit.MILLISECONDS); } LOGGER.info(String.format("Task %d has processed all %d records", this.taskIndex, this.totalRecords)); } finally{ try { this.context.close(); } catch (IOException ioe) { LOGGER.error("Failed to close context: " + this.context.getName(), ioe); } finally { this.countDownLatch.countDown(); } } } } }