#!/usr/bin/env python3 """ Helper script for running Sandcrawler (journal pipeline) tasks in production. This is basically a Makefile. If you edit this file, be sure to only use python3 standard library modules, so there are no dependencies. """ import sys import argparse import subprocess from datetime import datetime HDFS_DIR = "hdfs:///user/bnewbold/sandcrawler" HBASE_HOST = "wbgrp-svc263.us.archive.org" ZOOKEEPER_HOSTS = "mtrcs-zk1.us.archive.org:2181" GROBID_URI = "http://wbgrp-svc096.us.archive.org:8070" def rebuild_python(): print("Rebuilding python venv...") cmd = """cd python; export PIPENV_VENV_IN_PROJECT=1; pipenv install --deploy tar -czf venv-current.tar.gz -C .venv .""" subprocess.call(cmd, shell=True) def rebuild_scalding(): print("Rebuilding scalding jar...") cmd = """cd scalding; sbt assembly""" subprocess.call(cmd, shell=True) def run_backfill(args): if args.rebuild: rebuild_python() print("Starting backfill job...") output = "{}/output-{}/{}-backfill".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """cd python; pipenv run ./backfill_hbase_from_cdx.py \ --hbase-host {hbase_host} \ --hbase-table wbgrp-journal-extract-0-{env} \ -r hadoop \ -c mrjob.conf \ --archive venv-current.tar.gz#venv \ {input_cdx} """.format(hbase_host=HBASE_HOST, env=args.env, input_cdx=args.input_cdx) subprocess.call(cmd, shell=True) def run_extract(args): if args.rebuild: rebuild_python() print("Starting extract job...") output = "{}/output-{}/{}-extract".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """cd python; pipenv run ./extraction_cdx_grobid.py \ --hbase-host {hbase_host} \ --hbase-table wbgrp-journal-extract-0-{env} \ --grobid-uri {grobid_uri} \ -r hadoop \ -c mrjob.conf \ --output-dir {output} \ --no-output \ --archive venv-current.tar.gz#venv \ --jobconf mapred.line.input.format.linespermap=8000 \ --jobconf mapreduce.job.queuename=extraction \ --jobconf mapred.task.timeout=3600000 \ {input_cdx} """.format(hbase_host=HBASE_HOST, env=args.env, output=output, input_cdx=args.input_cdx, grobid_uri=GROBID_URI) subprocess.call(cmd, shell=True) def run_extract_ungrobided(args): if args.rebuild: rebuild_python() print("Starting extractungrobided job...") output = "{}/output-{}/{}-extract-ungrobided".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """cd python; pipenv run ./extraction_ungrobided.py \ --hbase-host {hbase_host} \ --hbase-table wbgrp-journal-extract-0-{env} \ --grobid-uri {grobid_uri} \ -r hadoop \ -c mrjob.conf \ --output-dir {output} \ --no-output \ --archive venv-current.tar.gz#venv \ --jobconf mapred.line.input.format.linespermap=8000 \ --jobconf mapreduce.job.queuename=extraction \ --jobconf mapred.task.timeout=3600000 \ {input_ungrobided} """.format(hbase_host=HBASE_HOST, env=args.env, input_ungrobided=args.input_ungrobided, output=output, grobid_uri=GROBID_URI) subprocess.call(cmd, shell=True) def run_rowcount(args): if args.rebuild: rebuild_scalding() print("Starting rowcount job...") output = "{}/output-{}/{}-rowcount".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool sandcrawler.HBaseRowCountJob \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --output {output}""".format( output=output, zookeeper_hosts=ZOOKEEPER_HOSTS, env=args.env) subprocess.call(cmd, shell=True) def run_statuscodecount(args): if args.rebuild: rebuild_scalding() print("Starting statuscodecount job...") output = "{}/output-{}/{}-statuscodecount".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool sandcrawler.HBaseStatusCodeCountJob \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --output {output}""".format( output=output, zookeeper_hosts=ZOOKEEPER_HOSTS, env=args.env) subprocess.call(cmd, shell=True) def run_statuscount(args): if args.rebuild: rebuild_scalding() print("Starting statuscount job...") output = "{}/output-{}/{}-statuscount".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool sandcrawler.HBaseStatusCountJob \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --output {output}""".format( output=output, zookeeper_hosts=ZOOKEEPER_HOSTS, env=args.env) subprocess.call(cmd, shell=True) def run_matchcrossref(args): if args.rebuild: rebuild_scalding() print("Starting matchcrossref job...") output = "{}/output-{}/{}-matchcrossref".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) if args.fatcat_insertable: jobclass = "ScoreInsertableJob" else: jobclass = "ScoreJob" # Notes: -D options must come after Tool but before class name # https://github.com/twitter/scalding/wiki/Frequently-asked-questions#how-do-i-pass-parameters-to-my-hadoop-job-number-of-reducers--memory-options--etc- # Compression: changed due to errors in production # https://stackoverflow.com/a/11336820/4682349 cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool \ -Dmapred.reduce.tasks={reducers} \ -Dcascading.spill.list.threshold=500000 \ -D mapred.output.compress=false \ -Dmapred.compress.map.output=true\ sandcrawler.{jobclass} \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --crossref-input {crossref_input} \ --output {output}""".format( output=output, jobclass=jobclass, zookeeper_hosts=ZOOKEEPER_HOSTS, env=args.env, reducers=args.reducers, crossref_input=args.crossref_input) subprocess.call(cmd, shell=True) def run_grobidscorabledump(args): if args.rebuild: rebuild_scalding() print("Starting grobid-scorable-dump job...") output = "{}/output-{}/{}-grobidscorabledump".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool sandcrawler.GrobidScorableDumpJob \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --output {output}""".format( output=output, zookeeper_hosts=ZOOKEEPER_HOSTS, env=args.env) subprocess.call(cmd, shell=True) def run_colcount(args): if args.rebuild: rebuild_scalding() print("Starting colcount job...") output = "{}/output-{}/{}-colcount-{}".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S"), args.column.replace(':', '_')) cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool sandcrawler.HBaseColCountJob \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --column {column} \ --output {output}""".format( column=args.column, output=output, zookeeper_hosts=ZOOKEEPER_HOSTS, env=args.env) subprocess.call(cmd, shell=True) def run_matchbenchmark(args): if args.rebuild: rebuild_scalding() print("Starting matchbenchmark job...") cmd = """./pig/deps/hadoop/bin/hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool \ sandcrawler.MatchBenchmarkJob \ --local \ --app.conf.path scalding/ia_cluster.conf \ --left-bibjson {left_bibjson} \ --right-bibjson {right_bibjson} \ --output {output}""".format( output=args.output, left_bibjson=args.left_bibjson, right_bibjson=args.right_bibjson) subprocess.call(cmd, shell=True) def run_keysmissingcol(args): if args.rebuild: rebuild_scalding() print("Starting keysmissingcol job...") output = "{}/output-{}/{}-keysmissingcol-{}".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S"), args.column.replace(":", "_")) cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool sandcrawler.MissingColumnDumpJob \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --column {column} \ --output {output}""".format( output=output, zookeeper_hosts=ZOOKEEPER_HOSTS, column=args.column, env=args.env) subprocess.call(cmd, shell=True) def run_dumpungrobided(args): if args.rebuild: rebuild_scalding() print("Starting dumpungrobided job...") output = "{}/output-{}/{}-dumpungrobided".format( HDFS_DIR, args.env, datetime.strftime(datetime.now(), "%Y-%m-%d-%H%M.%S")) cmd = """hadoop jar \ scalding/target/scala-2.11/sandcrawler-assembly-0.2.0-SNAPSHOT.jar \ com.twitter.scalding.Tool sandcrawler.DumpUnGrobidedJob \ --hdfs \ --app.conf.path scalding/ia_cluster.conf \ --hbase-table wbgrp-journal-extract-0-{env} \ --zookeeper-hosts {zookeeper_hosts} \ --output {output}""".format( output=output, zookeeper_hosts=ZOOKEEPER_HOSTS, env=args.env) subprocess.call(cmd, shell=True) def main(): parser = argparse.ArgumentParser() parser.add_argument('--prod', help="run against prod HBase table", action='store_true') parser.add_argument('--qa', help="run against qa HBase table", action='store_true') parser.add_argument('--rebuild', help="rebuild whatever artifact gets sent", action='store_true') subparsers = parser.add_subparsers() sub_backfill = subparsers.add_parser('backfill') sub_backfill.set_defaults(func=run_backfill) sub_backfill.add_argument('input_cdx', help="full HDFS path of CDX file to backfill") sub_extract = subparsers.add_parser('extract') sub_extract.set_defaults(func=run_extract) sub_extract.add_argument('input_cdx', help="full HDFS path of CDX file to extract") sub_extractungrobided = subparsers.add_parser('extract-ungrobided') sub_extractungrobided.set_defaults(func=run_extract_ungrobided) sub_extractungrobided.add_argument('input_ungrobided', help="full HDFS path of 'ungrobided' file to extract") sub_rowcount = subparsers.add_parser('row-count') sub_rowcount.set_defaults(func=run_rowcount) sub_statuscount = subparsers.add_parser('status-count') sub_statuscount.set_defaults(func=run_statuscount) sub_statuscodecount = subparsers.add_parser('status-code-count') sub_statuscodecount.set_defaults(func=run_statuscodecount) sub_matchcrossref = subparsers.add_parser('match-crossref') sub_matchcrossref.set_defaults(func=run_matchcrossref) sub_matchcrossref.add_argument('crossref_input', help="full HDFS path of Crossref JSON dump") sub_matchcrossref.add_argument('--reducers', help="number of reducers to run", type=int, default=200) sub_matchcrossref.add_argument('--fatcat-insertable', help="whether to include CDX and other metadata in output", action='store_true') sub_grobidscorabledump = subparsers.add_parser('grobid-scorable-dump') sub_grobidscorabledump.set_defaults(func=run_grobidscorabledump) sub_colcount = subparsers.add_parser('col-count') sub_colcount.set_defaults(func=run_colcount) sub_colcount.add_argument('column', help="column name to use in count") sub_matchbenchmark = subparsers.add_parser('match-benchmark') sub_matchbenchmark.set_defaults(func=run_matchbenchmark) sub_matchbenchmark.add_argument('left_bibjson', help="First bibjson file") sub_matchbenchmark.add_argument('right_bibjson', help="Second bibjson file") sub_matchbenchmark.add_argument('output', help="where to write output") sub_keysmissingcol = subparsers.add_parser('keys-missing-col') sub_keysmissingcol.set_defaults(func=run_keysmissingcol) sub_keysmissingcol.add_argument('column', help="column to SCAN for missing keys") sub_dumpungrobided = subparsers.add_parser('dump-ungrobided') sub_dumpungrobided.set_defaults(func=run_dumpungrobided) args = parser.parse_args() if not args.__dict__.get("func"): print("tell me what to do! (try --help)") sys.exit(-1) if not (args.prod or args.qa) or (args.prod and args.qa): print("must pass one of --prod or --qa") sys.exit(-1) if args.prod: args.env = "prod" if args.qa: args.env = "qa" args.func(args) if __name__ == '__main__': main()