All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.opentsdb.tools.TreeSync Maven / Gradle / Ivy

Go to download

OpenTSDB is a distributed, scalable Time Series Database (TSDB) written on top of HBase. OpenTSDB was written to address a common need: store, index and serve metrics collected from computer systems (network gear, operating systems, applications) at a large scale, and make this data easily accessible and graphable.

There is a newer version: 2.4.1
Show newest version
// This file is part of OpenTSDB.
// Copyright (C) 2013  The OpenTSDB Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at your
// option) any later version.  This program is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.  You should have received a copy
// of the GNU Lesser General Public License along with this program.  If not,
// see .
package net.opentsdb.tools;

import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.opentsdb.core.TSDB;
import net.opentsdb.meta.TSMeta;
import net.opentsdb.tree.Tree;
import net.opentsdb.tree.TreeBuilder;
import net.opentsdb.uid.NoSuchUniqueId;
import net.opentsdb.uid.UniqueId;
import net.opentsdb.utils.JSON;

import org.hbase.async.Bytes;
import org.hbase.async.HBaseException;
import org.hbase.async.KeyValue;
import org.hbase.async.Scanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.stumbleupon.async.Callback;
import com.stumbleupon.async.Deferred;

/**
 * Helper tool class used to generate or synchronize a tree using TSMeta objects
 * stored in the UID table. Also can be used to delete a tree. This class should
 * be used only by the CLI tools.
 */
final class TreeSync extends Thread {
  private static final Logger LOG = LoggerFactory.getLogger(TreeSync.class);
  
  /** Charset used to convert Strings to byte arrays and back. */
  private static final Charset CHARSET;
  static {
    final Class uidclass = UniqueId.class;
    try {
      // Those are all implementation details so they're not part of the
      // interface.  We access them anyway using reflection.  I think this
      // is better than marking those public and adding a javadoc comment
      // "THIS IS INTERNAL DO NOT USE".  If only Java had C++'s "friend" or
      // a less stupid notion of a package.
      Field f;
      f = uidclass.getDeclaredField("CHARSET");
      f.setAccessible(true);
      CHARSET = (Charset) f.get(null);
    } catch (Exception e) {
      throw new RuntimeException("static initializer failed", e);
    }
  }
  
  /** TSDB to use for storage access */
  final TSDB tsdb;
  
  /** The ID to start the sync with for this thread */
  final long start_id;
  
  /** The end of the ID block to work on */
  final long end_id;
  
  /** Diagnostic ID for this thread */
  final int thread_id;
  
  /**
   * Default constructor, stores the TSDB to use
   * @param tsdb The TSDB to use for access
   * @param start_id The starting ID of the block we'll work on
   * @param quotient The total number of IDs in our block
   * @param thread_id The ID of this thread (starts at 0)
   */
  public TreeSync(final TSDB tsdb, final long start_id, final double quotient,
      final int thread_id) {
    this.tsdb = tsdb;
    this.start_id = start_id;
    this.end_id = start_id + (long) quotient + 1; // teensy bit of overlap
    this.thread_id = thread_id;
  }
  
  /**
   * Performs a tree synchronization using a table scanner across the UID table
   * @return 0 if completed successfully, something else if an error occurred
   */
  public void run() {
    final Scanner scanner = getScanner();

    // start the process by loading all of the trees in the system
    final List trees;
    try {
      trees = Tree.fetchAllTrees(tsdb).joinUninterruptibly();
      LOG.info("[" + thread_id + "] Complete");
    } catch (Exception e) {
      LOG.error("[" + thread_id + "] Unexpected Exception", e);
      throw new RuntimeException("[" + thread_id + "] Unexpected exception", e);
    }
    
    if (trees == null) {
      LOG.warn("No tree definitions were found");
      return;
    } else {
      boolean has_enabled_tree = false;
      for (Tree tree : trees) {
        if (tree.getEnabled()) {
          has_enabled_tree = true;
          break;
        }
      }
      if (!has_enabled_tree) {
        LOG.warn("No enabled trees were found");
        return;
      }
      LOG.info("Found [" + trees.size() + "] trees");
    }
    
    // setup an array for storing the tree processing calls so we can block 
    // until each call has completed
    final ArrayList> tree_calls = 
      new ArrayList>();
    
    final Deferred completed = new Deferred();

    /**
     * Scanner callback that loops through the UID table recursively until 
     * the scanner returns a null row set.
     */
    final class TsuidScanner implements Callback, 
      ArrayList>> {

      /**
       * Fetches the next set of rows from the scanner, adding this class as a 
       * callback
       * @return A meaningless deferred used to wait on until processing has
       * completed
       */
      public Deferred scan() {
        return scanner.nextRows().addCallbackDeferring(this);
      }
      
      @Override
      public Deferred call(ArrayList> rows)
          throws Exception {
        if (rows == null) {
          completed.callback(true);
          return null;
        }
        
        for (final ArrayList row : rows) {
          // convert to a string one time
          final String tsuid = UniqueId.uidToString(row.get(0).key());
          
          /**
           * A throttling callback used to wait for the current TSMeta to 
           * complete processing through the trees before continuing on with 
           * the next set.
           */
          final class TreeBuilderBufferCB implements Callback>> {

            @Override
            public Boolean call(ArrayList> builder_calls)
                throws Exception {
              //LOG.debug("Processed [" + builder_calls.size() + "] tree_calls");
              return true;
            }
            
          }
          
          /**
           * Executed after parsing a TSMeta object and loading all of the
           * associated UIDMetas. Once the meta has been loaded, this callback
           * runs it through each of the configured TreeBuilder objects and
           * stores the resulting deferred in an array. Once processing of all
           * of the rules has completed, we group the deferreds and call
           * BufferCB() to wait for their completion.
           */
          final class ParseCB implements Callback, TSMeta> {

            final ArrayList>> builder_calls = 
              new ArrayList>>();
            
            @Override
            public Deferred call(TSMeta meta) throws Exception {
              if (meta != null) {
                LOG.debug("Processing TSMeta: " + meta + " w value: " + 
                    JSON.serializeToString(meta));
                
                // copy the trees into a tree builder object and iterate through
                // each builder. We need to do this as a builder is not thread
                // safe and cannot be used asynchronously.
                final ArrayList tree_builders = 
                new ArrayList(trees.size());
                for (Tree tree : trees) {
                  if (!tree.getEnabled()) {
                    continue;
                  }
                  final TreeBuilder builder = new TreeBuilder(tsdb, tree);
                  tree_builders.add(builder);
                }
                
                for (TreeBuilder builder : tree_builders) {
                  builder_calls.add(builder.processTimeseriesMeta(meta));
                }
                return Deferred.group(builder_calls)
                  .addCallback(new TreeBuilderBufferCB());
              } else {
                return Deferred.fromResult(false);
              }
            }
            
          }
          
          /**
           * An error handler used to catch issues when loading the TSMeta such
           * as a missing UID name. In these situations we want to log that the 
           * TSMeta had an issue and continue on.
           */
          final class ErrBack implements Callback, Exception> {
            
            @Override
            public Deferred call(Exception e) throws Exception {
              
              if (e.getClass().equals(IllegalStateException.class)) {
                LOG.error("Invalid data when processing TSUID [" + tsuid + "]", e);
              } else if (e.getClass().equals(IllegalArgumentException.class)) {
                LOG.error("Invalid data when processing TSUID [" + tsuid + "]", e);
              } else if (e.getClass().equals(NoSuchUniqueId.class)) {
                LOG.warn("Timeseries [" + tsuid + 
                    "] includes a non-existant UID: " + e.getMessage());
              } else {
                LOG.error("[" + thread_id + "] Exception while processing TSUID [" + 
                    tsuid + "]", e);
              }
              
              return Deferred.fromResult(false);
            }
            
          }

          // matched a TSMeta column, so request a parsing and loading of
          // associated UIDMeta objects, then pass it off to callbacks for 
          // parsing through the trees.
          final Deferred process_tsmeta = 
            TSMeta.parseFromColumn(tsdb, row.get(0), true)
              .addCallbackDeferring(new ParseCB());
          process_tsmeta.addErrback(new ErrBack());
          tree_calls.add(process_tsmeta);
        }
        
        /**
         * Another buffer callback that waits for the current set of TSMetas to
         * complete their tree calls before we fetch another set of rows from
         * the scanner. This necessary to avoid OOM issues.
         */
        final class ContinueCB implements Callback, 
          ArrayList> {

          @Override
          public Deferred call(ArrayList tsuids)
              throws Exception {
            LOG.debug("Processed [" + tsuids.size() + "] tree_calls, continuing");
            tree_calls.clear();
            return scan();
          }
          
        }
        
        // request the next set of rows from the scanner, but wait until the
        // current set of TSMetas has been processed so we don't slaughter our
        // host
        Deferred.group(tree_calls).addCallback(new ContinueCB());
        return Deferred.fromResult(null);
      }
      
    }
    
    /**
     * Used to capture unhandled exceptions from the scanner callbacks and 
     * exit the thread properly
     */
    final class ErrBack implements Callback, Exception> {
      
      @Override
      public Deferred call(Exception e) throws Exception {
        LOG.error("Unexpected exception", e);
        completed.callback(false);
        return Deferred.fromResult(false);
      }
      
    }
    
    final TsuidScanner tree_scanner = new TsuidScanner();
    tree_scanner.scan().addErrback(new ErrBack());
    try {
      completed.joinUninterruptibly();
      LOG.info("[" + thread_id + "] Complete");
    } catch (Exception e) {
      LOG.error("[" + thread_id + "] Scanner Exception", e);
      throw new RuntimeException("[" + thread_id + "] Scanner exception", e);
    }
    return;
  }

  /**
   * Attempts to delete all data generated by the given tree, and optionally,
   * the tree definition itself.
   * @param tree_id The tree with data to delete
   * @param delete_definition Whether or not the tree definition itself should
   * be removed from the system
   * @return 0 if completed successfully, something else if an error occurred
   */
  public int purgeTree(final int tree_id, final boolean delete_definition) 
    throws Exception {
    if (delete_definition) {
      LOG.info("Deleting tree branches and definition for: " + tree_id);
    } else {
      LOG.info("Deleting tree branches for: " + tree_id);
    }
    Tree.deleteTree(tsdb, tree_id, delete_definition).joinUninterruptibly();
    LOG.info("Completed tree deletion for: " + tree_id);
    return 0;
  }

  /**
   * Returns a scanner set to scan the range configured for this thread
   * @return A scanner on the "name" CF configured for the specified range
   * @throws HBaseException if something goes boom
   */
  private Scanner getScanner() throws HBaseException {
    final short metric_width = TSDB.metrics_width();
    final byte[] start_row = 
      Arrays.copyOfRange(Bytes.fromLong(start_id), 8 - metric_width, 8);
    final byte[] end_row = 
      Arrays.copyOfRange(Bytes.fromLong(end_id), 8 - metric_width, 8);

    LOG.debug("[" + thread_id + "] Start row: " + UniqueId.uidToString(start_row));
    LOG.debug("[" + thread_id + "] End row: " + UniqueId.uidToString(end_row));
    final Scanner scanner = tsdb.getClient().newScanner(tsdb.metaTable());
    scanner.setStartKey(start_row);
    scanner.setStopKey(end_row);
    scanner.setFamily("name".getBytes(CHARSET));
    scanner.setQualifier("ts_meta".getBytes(CHARSET));
    return scanner;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy