/** * Copyright 2012 - CommonCrawl Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * **/ package org.commoncrawl.mapred.ec2.postprocess.crawldb; import java.io.IOException; import java.nio.charset.Charset; import java.text.NumberFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DataOutputBuffer; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.OutputCollector; import org.apache.hadoop.mapred.Reducer; import org.apache.hadoop.mapred.Reporter; import org.commoncrawl.mapred.ec2.postprocess.crawldb.CrawlDBKey.ComponentId; import org.commoncrawl.protocol.URLFPV2; import org.commoncrawl.util.ByteArrayUtils; import org.commoncrawl.util.GoogleURL; import org.commoncrawl.util.HttpHeaderInfoExtractor; import org.commoncrawl.util.TextBytes; import org.commoncrawl.util.URLFPBloomFilter; import org.commoncrawl.util.URLUtils; import static org.commoncrawl.util.JSONUtils.*; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** * map reduce job that produces a crawldb given link graph/crawl status data emitted * from both the LinkGraphDataEmitter job and previous runs of the CrawlDBWriter itself. * * @author rana * */ public class CrawlDBMergingReducer implements Reducer<TextBytes, TextBytes ,TextBytes,TextBytes>, CrawlDBCommon { static final Log LOG = LogFactory.getLog(CrawlDBMergingReducer.class); // The crawldb job emits data in the form a JSON data structure // The top level JSON object contains optionally, a link_status object, a summary object // and a source_url string. // The Summary object has the properties defined by the SUMMARYRECORD_ constant prefix. // The LinkStatus object has properties defined by the LINKSTATUS_ prefix // The Summary object can contain zero to N CrawlDetail objects, one for each // crawl attempt. The properties defined by CrawlDetail object are prefixed with // the CRAWLDETAIL_ prefix. /////////////////////////////////////////////////////////////////////////// // EC2 PATHS /////////////////////////////////////////////////////////////////////////// static final String S3N_BUCKET_PREFIX = "s3n://aws-publicdatasets"; static final String MERGE_INTERMEDIATE_OUTPUT_PATH = "/common-crawl/crawl-db/intermediate/"; static final String MERGE_DB_PATH = "/common-crawl/crawl-db/mergedDB/"; /////////////////////////////////////////////////////////////////////////// // CONSTANTS /////////////////////////////////////////////////////////////////////////// static final int MAX_TYPE_SAMPLES = 5; static final int DEFAULT_OUTGOING_URLS_BUFFER_SIZE = 1 << 18; // 262K static final int DEFAULT_OUTGOING_URLS_BUFFER_PAD_AMOUNT = 16384; static final int DEFAULT_EXT_SOURCE_SAMPLE_BUFFER_SIZE = 1 << 27; // 134 MB static final int DEFAULT_EXT_SOURCE_SAMPLE_BUFFER_PAD_AMOUNT = 16384; static final int MAX_EXTERNALLY_REFERENCED_URLS = 100; //private int OUTGOING_URLS_BUFFER_SIZE = DEFAULT_OUTGOING_URLS_BUFFER_SIZE; //private int OUTGOING_URLS_BUFFER_PAD_AMOUNT =DEFAULT_OUTGOING_URLS_BUFFER_PAD_AMOUNT; private int EXT_SOURCE_SAMPLE_BUFFER_SIZE = DEFAULT_EXT_SOURCE_SAMPLE_BUFFER_SIZE; private int EXT_SOURCE_SAMPLE_BUFFER_PAD_AMOUNT = DEFAULT_EXT_SOURCE_SAMPLE_BUFFER_PAD_AMOUNT; /////////////////////////////////////////////////////////////////////////// // Counters /////////////////////////////////////////////////////////////////////////// enum Counters { FAILED_TO_GET_LINKS_FROM_HTML, NO_HREF_FOR_HTML_LINK, EXCEPTION_IN_MAP, GOT_HTML_METADATA, GOT_FEED_METADATA, EMITTED_ATOM_LINK, EMITTED_HTML_LINK, EMITTED_RSS_LINK, GOT_PARSED_AS_ATTRIBUTE, GOT_LINK_OBJECT, NULL_CONTENT_OBJECT, NULL_LINKS_ARRAY, FP_NULL_IN_EMBEDDED_LINK, SKIPPED_ALREADY_EMITTED_LINK, FOUND_HTTP_DATE_HEADER, FOUND_HTTP_AGE_HEADER, FOUND_HTTP_LAST_MODIFIED_HEADER, FOUND_HTTP_EXPIRES_HEADER, FOUND_HTTP_CACHE_CONTROL_HEADER, FOUND_HTTP_PRAGMA_HEADER, REDUCER_GOT_LINK, REDUCER_GOT_STATUS, ONE_REDUNDANT_LINK_IN_REDUCER, TWO_REDUNDANT_LINKS_IN_REDUCER, THREE_REDUNDANT_LINKS_IN_REDUCER, GT_THREE_REDUNDANT_LINKS_IN_REDUCER, ONE_REDUNDANT_STATUS_IN_REDUCER, TWO_REDUNDANT_STATUS_IN_REDUCER, THREE_REDUNDANT_STATUS_IN_REDUCER, GT_THREE_REDUNDANT_STATUS_IN_REDUCER, GOT_RSS_FEED, GOT_ATOM_FEED, GOT_ALTERNATE_LINK_FOR_ATOM_ITEM, GOT_CONTENT_FOR_ATOM_ITEM, GOT_ITEM_LINK_FROM_RSS_ITEM, GOT_TOP_LEVEL_LINK_FROM_RSS_ITEM, GOT_TOP_LEVEL_LINK_FROM_ATOM_ITEM, EMITTED_REDIRECT_RECORD, DISCOVERED_NEW_LINK, GOT_LINK_FOR_ITEM_WITH_STATUS, FAILED_TO_GET_SOURCE_HREF, GOT_CRAWL_STATUS_RECORD, GOT_EXTERNAL_DOMAIN_SOURCE, NO_SOURCE_URL_FOR_CRAWL_STATUS, OUTPUT_KEY_FROM_INTERNAL_LINK, OUTPUT_KEY_FROM_EXTERNAL_LINK, GOT_HTTP_200_CRAWL_STATUS, GOT_REDIRECT_CRAWL_STATUS, BAD_REDIRECT_URL, GOT_MERGED_RECORD, MERGED_OBJECT_FIRST_OBJECT, ADOPTED_SOURCE_SUMMARY_RECORD, MERGED_SOURCE_SUMMARY_RECORD_INTO_DEST, ADOPTED_SOURCE_LINKSUMMARY_RECORD, MERGED_SOURCE_LINKSUMMARY_RECORD_INTO_DEST, ALLOCATED_TOP_LEVEL_OBJECT_IN_FLUSH, ENCOUNTERED_EXISTING_TOP_LEVEL_OBJECT_IN_FLUSH, ENCOUNTERED_SUMMARY_RECORD_IN_FLUSH, ENCOUNTERED_LINKSUMMARY_RECORD_IN_FLUSH, EMITTED_SOURCEINPUTS_RECORD, GOT_NULL_REDIRECT_URL, INTERDOMAIN_LINKS_LTEQ_100, INTERDOMAIN_LINKS_LTEQ_1000, INTERDOMAIN_LINKS_GT_1000, EMITTED_SOURCEINPUTS_DATA_BYTES_EMITTED, INPUT_RECORD_COUNT, ADOPTED_NEW_BLEKKO_METADATA_RECORD, BLEKKO_METADATA_WITH_NO_SOURCE_CC_RECORD, MERGE_RECORD_HAS_BLEKKO_METADATA, EMITTED_RECORD_WITH_BLEKKO_METADATA, BLEKKO_RECORD_ALREADY_IN_DATABASE , BLEKKO_CRAWLED_CC_CRAWLED, BLEKKO_NOT_CRAWLED_CC_CRAWLED} /////////////////////////////////////////////////////////////////////////// // Data Members /////////////////////////////////////////////////////////////////////////// public static final int NUM_HASH_FUNCTIONS = 10; public static final int NUM_BITS = 11; public static final int NUM_ELEMENTS = 1 << 26; public static final int FLUSH_INTERVAL = 1 << 17; private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(); static { NUMBER_FORMAT.setMinimumIntegerDigits(5); NUMBER_FORMAT.setGroupingUsed(false); } // parser JsonParser _parser = new JsonParser(); // the top level object JsonObject _topLevelJSONObject; // the current summary record ... JsonObject _summaryRecord = null; // the current link summary record JsonObject _linkSummaryRecord = null; // collection of types detected for current url HashSet<String> _types = new HashSet<String>(); // collection of external references urls in current document HashSet<String> _extHrefs = new HashSet<String>(); // the url string to use as the output key ... String _outputKeyString = null; // freeze url key ... boolean _urlKeyForzen = false; // url object representing the current key GoogleURL _outputKeyURLObj = null; // source inputs tracking bloomfilter URLFPBloomFilter _sourceInputsTrackingFilter; // a count of the number of urls processed long _urlsProcessed = 0; // key used to test bloomfilter URLFPV2 _bloomFilterKey = new URLFPV2(); // captured job conf JobConf _conf; // file system FileSystem _fs; // partition id int _partitionId; //SequenceFile.Writer _redirectWriter = null; // input buffer used to collect referencing urls DataOutputBuffer _sourceInputsBuffer; // count of referencing domains int _sourceSampleSize = 0; // current input key URLFPV2 _currentKey = null; // temporary key used to transition input keys URLFPV2 _tempKey = new URLFPV2(); // cached collector pointer ... OutputCollector<TextBytes, TextBytes> _outputCollector; Reporter _reporter; @Override public void reduce(TextBytes keyBytes, Iterator<TextBytes> values,OutputCollector<TextBytes, TextBytes> output, Reporter reporter)throws IOException { if (_outputCollector == null) { _outputCollector = output; _reporter = reporter; } // potentially transition to new url readFPCheckForTransition(keyBytes,output,reporter); // extract link type .. long linkType = CrawlDBKey.getLongComponentFromKey(keyBytes,CrawlDBKey.ComponentId.TYPE_COMPONENT_ID); while (values.hasNext()) { reporter.incrCounter(Counters.INPUT_RECORD_COUNT, 1); TextBytes valueBytes = values.next(); //LOG.debug("ValueBytes:"+ valueBytes.toString()); if (linkType == CrawlDBKey.Type.KEY_TYPE_MERGED_RECORD.ordinal()) { reporter.incrCounter(Counters.GOT_MERGED_RECORD, 1); JsonObject mergedObject = _parser.parse(valueBytes.toString()).getAsJsonObject(); if (mergedObject != null) { setSourceURLFromJSONObject(mergedObject,linkType); processMergedRecord(mergedObject,_currentKey,reporter); } } else if (linkType == CrawlDBKey.Type.KEY_TYPE_CRAWL_STATUS.ordinal()) { reporter.incrCounter(Counters.GOT_CRAWL_STATUS_RECORD,1); try { JsonObject object = _parser.parse(valueBytes.toString()).getAsJsonObject(); if (object != null) { // update url key if necessary ... setSourceURLFromJSONObject(object,linkType); // emit a redirect record if necessary ... JsonElement redirectObject = object.get("redirect_from"); if (redirectObject != null) { emitRedirectRecord(object, redirectObject.getAsJsonObject(),output, reporter); } // get latest crawl time long latestCrawlTime = (_summaryRecord != null) ? safeGetLong(_summaryRecord,SUMMARYRECORD_LATEST_CRAWLTIME_PROPERTY) : -1; long attemptTime = safeGetLong(object, "attempt_time"); // if this is the latest crawl event, then we want to track the links associated with this crawl status ... HashSet<String> extHrefs = (attemptTime > latestCrawlTime) ? _extHrefs : null; // create a crawl detail record from incoming JSON JsonObject crawlDetail = crawlDetailRecordFromCrawlStatusRecord(object,_currentKey,extHrefs,reporter); // add to our list of crawl detail records ... safeAddCrawlDetailToSummaryRecord(crawlDetail); // ok, now update summary stats based on incoming crawl detail record ... updateSummaryRecordFromCrawlDetailRecord(crawlDetail,_currentKey,reporter); } } catch (Exception e) { LOG.error("Error Parsing JSON:" + valueBytes.toString()); throw new IOException(e); } break; } else if (linkType >= CrawlDBKey.Type.KEY_TYPE_HTML_LINK.ordinal() && linkType <= CrawlDBKey.Type.KEY_TYPE_RSS_LINK.ordinal()) { JsonObject object = _parser.parse(valueBytes.toString()).getAsJsonObject(); if (object != null) { setSourceURLFromJSONObject(object,linkType); // LOG.debug("Got LinkData:" + JSONUtils.prettyPrintJSON(object)); // ok this is a link ... updateLinkStatsFromLinkJSONObject(object,_currentKey,reporter); } } else if (linkType == CrawlDBKey.Type.KEY_TYPE_INCOMING_URLS_SAMPLE.ordinal()) { importLinkSourceData(_currentKey, valueBytes); } reporter.progress(); } } @Override public void configure(JobConf job) { _sourceInputsBuffer = new DataOutputBuffer(EXT_SOURCE_SAMPLE_BUFFER_SIZE); _sourceInputsTrackingFilter = new URLFPBloomFilter(NUM_ELEMENTS, NUM_HASH_FUNCTIONS, NUM_BITS); _conf = job; try { _fs = FileSystem.get(_conf); _partitionId = _conf.getInt("mapred.task.partition", 0); } catch (IOException e) { e.printStackTrace(); } } @Override public void close() throws IOException { flushCurrentRecord(_outputCollector,_reporter); } /** * internal helper - emit a redirect record give a source crawl status record * * @param jsonObject * @param redirectObj * @param output * @param reporter * @throws IOException */ void emitRedirectRecord(JsonObject jsonObject,JsonObject redirectObj,OutputCollector<TextBytes, TextBytes> output,Reporter reporter)throws IOException { // ok first things first, generate a fingerprint for redirect SOURCE URLFPV2 redirectFP = URLUtils.getURLFPV2FromURL(redirectObj.get("source_url").getAsString()); if (redirectFP == null) { reporter.incrCounter(Counters.BAD_REDIRECT_URL, 1); } else { int httpResult = redirectObj.get("http_result").getAsInt(); JsonObject redirectJSON = new JsonObject(); redirectJSON.addProperty("disposition","SUCCESS"); redirectJSON.addProperty("http_result",httpResult); redirectJSON.addProperty("server_ip",redirectObj.get("server_ip").getAsString()); redirectJSON.addProperty("attempt_time",jsonObject.get("attempt_time").getAsLong()); redirectJSON.addProperty("target_url",jsonObject.get("source_url").getAsString()); redirectJSON.addProperty("source_url",redirectObj.get("source_url").getAsString()); // ok emit the redirect record ... TextBytes key = CrawlDBKey.generateKey(redirectFP,CrawlDBKey.Type.KEY_TYPE_CRAWL_STATUS,jsonObject.get("attempt_time").getAsLong()); LOG.debug("!!!!!!Emitting Redirect Record:" + redirectJSON.toString()); output.collect(key, new TextBytes(redirectJSON.toString())); reporter.incrCounter(Counters.EMITTED_REDIRECT_RECORD, 1); //_redirectWriter.append(new TextBytes(redirectObj.get("source_url").getAsString()), new TextBytes(redirectJSON.toString())); } } /** * grab date headers and incorporate them into the crawl detail object * * @param jsonObject * @param crawlStatsJSON */ static void populateDateHeadersFromJSONObject(JsonObject jsonObject,JsonObject crawlStatsJSON) { JsonObject headers = jsonObject.getAsJsonObject("http_headers"); if (headers != null) { JsonElement httpDate = headers.get("date"); JsonElement age = headers.get("age"); JsonElement lastModified = headers.get("last-modified"); JsonElement expires = headers.get("expires"); JsonElement cacheControl = headers.get("cache-control"); JsonElement pragma = headers.get("pragma"); JsonElement etag = headers.get("etag"); if (httpDate != null) { crawlStatsJSON.addProperty(CRAWLDETAIL_HTTP_DATE_PROPERTY, HttpHeaderInfoExtractor.getTime(httpDate.getAsString())); } if (age != null) { crawlStatsJSON.add(CRAWLDETAIL_HTTP_AGE_PROPERTY, age); } if (lastModified != null) { crawlStatsJSON.addProperty(CRAWLDETAIL_HTTP_LAST_MODIFIED_PROPERTY, HttpHeaderInfoExtractor.getTime(lastModified.getAsString())); } if (expires != null) { crawlStatsJSON.addProperty(CRAWLDETAIL_HTTP_EXPIRES_PROPERTY, HttpHeaderInfoExtractor.getTime(expires.getAsString())); } if (cacheControl != null) { crawlStatsJSON.add(CRAWLDETAIL_HTTP_CACHE_CONTROL_PROPERTY, cacheControl); } if (pragma != null) { crawlStatsJSON.add(CRAWLDETAIL_HTTP_PRAGMA_PROPERTY, pragma); } if (etag != null) { crawlStatsJSON.add(CRAWLDETAIL_HTTP_ETAG_PROPERTY, etag); } } } /** * * @param contentObj * @param crawlStatsJSON */ static void addMinMaxFeedItemTimes(JsonObject contentObj,JsonObject crawlStatsJSON) { JsonArray items = contentObj.getAsJsonArray("items"); if (items != null) { long minPubDate = -1L; long maxPubDate = -1L; int itemCount = 0; for (JsonElement item : items) { long pubDateValue = -1; JsonElement pubDate = item.getAsJsonObject().get("published"); if (pubDate != null) { pubDateValue = pubDate.getAsLong(); } JsonElement updateDate = item.getAsJsonObject().get("updated"); if (updateDate != null) { if (updateDate.getAsLong() > pubDateValue) { pubDateValue = updateDate.getAsLong(); } } if (minPubDate == -1L || pubDateValue < minPubDate) { minPubDate = pubDateValue; } if (maxPubDate == -1L || pubDateValue > maxPubDate) { maxPubDate = pubDateValue; } itemCount++; } crawlStatsJSON.addProperty(RSS_MIN_PUBDATE_PROPERTY,minPubDate); crawlStatsJSON.addProperty(RSS_MAX_PUBDATE_PROPERTY,maxPubDate); crawlStatsJSON.addProperty(RSS_ITEM_COUNT_PROPERTY,itemCount); } } /** * we need to extract source url from the JSON because it is not available via * the key * * @param jsonObject * @param keyType */ void setSourceURLFromJSONObject(JsonObject jsonObject, long keyType) { if (!_urlKeyForzen) { JsonElement sourceElement = jsonObject.get("source_url"); if (keyType == CrawlDBKey.Type.KEY_TYPE_CRAWL_STATUS.ordinal()) { _outputKeyString = sourceElement.getAsString(); _outputKeyURLObj = new GoogleURL(_outputKeyString); JsonElement httpResultElem = jsonObject.get("http_result"); if (httpResultElem != null) { int httpResult = httpResultElem.getAsInt(); if (httpResult >= 200 && httpResult <= 299) { if (sourceElement != null && _outputKeyString == null) { _outputKeyString = sourceElement.getAsString(); _outputKeyURLObj = new GoogleURL(_outputKeyString); if (_outputKeyURLObj.isValid()) _urlKeyForzen = true; } } } } else if (keyType == CrawlDBKey.Type.KEY_TYPE_MERGED_RECORD.ordinal()) { _outputKeyString = sourceElement.getAsString(); _outputKeyURLObj = new GoogleURL(_outputKeyString); _urlKeyForzen = true; } else if (keyType >= CrawlDBKey.Type.KEY_TYPE_HTML_LINK.ordinal() && keyType <= CrawlDBKey.Type.KEY_TYPE_RSS_LINK.ordinal()) { if (_outputKeyString == null) { JsonElement hrefElement = jsonObject.get("href"); if (sourceElement != null && hrefElement != null) { GoogleURL hrefSource = new GoogleURL(sourceElement.getAsString()); if (hrefSource.isValid()) { _outputKeyString = hrefElement.getAsString(); _outputKeyURLObj = new GoogleURL(_outputKeyString); } } } } } } void mergeBlekkoMetadata(JsonObject newBlekkoMetadata,JsonObject existingTopLevelObj,Reporter reporter) { if (newBlekkoMetadata != null) { if (!existingTopLevelObj.has(TOPLEVEL_BLEKKO_METADATA_PROPERTY)) { existingTopLevelObj.add(TOPLEVEL_BLEKKO_METADATA_PROPERTY,newBlekkoMetadata); } else { JsonObject existingBlkkoMetadata = existingTopLevelObj.getAsJsonObject(TOPLEVEL_BLEKKO_METADATA_PROPERTY); long existingTimestamp = existingBlkkoMetadata.get(BLEKKO_METADATA_TIMESTAMP_PROPERTY).getAsLong(); long newTimestamp = newBlekkoMetadata.get(BLEKKO_METADATA_TIMESTAMP_PROPERTY).getAsLong(); if (newTimestamp > existingTimestamp){ existingTopLevelObj.add(TOPLEVEL_BLEKKO_METADATA_PROPERTY, newBlekkoMetadata); reporter.incrCounter(Counters.ADOPTED_NEW_BLEKKO_METADATA_RECORD, 1); } } } } void mergeLinkRecords(JsonObject sourceRecord,JsonObject topLevelJSONObject,Reporter reporter) { JsonElement destRecord = topLevelJSONObject.get(TOPLEVEL_LINKSTATUS_PROPERTY); if (destRecord == null) { if (sourceRecord != null) { reporter.incrCounter(Counters.ADOPTED_SOURCE_LINKSUMMARY_RECORD, 1); topLevelJSONObject.add(TOPLEVEL_LINKSTATUS_PROPERTY,sourceRecord); JsonArray typeAndRels = sourceRecord.getAsJsonArray(LINKSTATUS_TYPEANDRELS_PROPERTY); if (typeAndRels != null) { for (JsonElement typeAndRel : typeAndRels) { _types.add(typeAndRel.getAsString()); } } } } else { if (sourceRecord != null) { reporter.incrCounter(Counters.MERGED_SOURCE_LINKSUMMARY_RECORD_INTO_DEST, 1); safeIncrementJSONCounter(destRecord.getAsJsonObject(),LINKSTATUS_INTRADOMAIN_SOURCES_COUNT_PROPERTY,sourceRecord.get(LINKSTATUS_INTRADOMAIN_SOURCES_COUNT_PROPERTY)); safeIncrementJSONCounter(destRecord.getAsJsonObject(),LINKSTATUS_EXTRADOMAIN_SOURCES_COUNT_PROPERTY,sourceRecord.get(LINKSTATUS_EXTRADOMAIN_SOURCES_COUNT_PROPERTY)); safeSetMinLongValue(destRecord.getAsJsonObject(),LINKSTATUS_EARLIEST_DATE_PROPERTY,sourceRecord.get(LINKSTATUS_EARLIEST_DATE_PROPERTY)); safeSetMaxLongValue(destRecord.getAsJsonObject(),LINKSTATUS_LATEST_DATE_PROPERTY,sourceRecord.get(LINKSTATUS_LATEST_DATE_PROPERTY)); JsonArray typeAndRels = sourceRecord.getAsJsonArray(LINKSTATUS_TYPEANDRELS_PROPERTY); if (typeAndRels != null) { for (JsonElement typeAndRel : typeAndRels) { _types.add(typeAndRel.getAsString()); } } } } } /** * merge two crawl summary records * @param incomingRecord * @param topLevelJSONObject * @param reporter * @throws IOException */ void mergeSummaryRecords(JsonObject incomingRecord,JsonObject topLevelJSONObject,Reporter reporter)throws IOException { JsonObject destinationSummaryRecord = topLevelJSONObject.getAsJsonObject(TOPLEVEL_SUMMARYRECORD_PROPRETY); if (destinationSummaryRecord == null) { if (incomingRecord != null) { reporter.incrCounter(Counters.ADOPTED_SOURCE_SUMMARY_RECORD, 1); // adopt source ... topLevelJSONObject.add(TOPLEVEL_SUMMARYRECORD_PROPRETY,incomingRecord); _summaryRecord = incomingRecord; } } else { if (incomingRecord != null) { reporter.incrCounter(Counters.MERGED_SOURCE_SUMMARY_RECORD_INTO_DEST, 1); // walk crawl detail records in incoming record and merge them into destination record ... JsonElement crawlStatsArray = incomingRecord.get(SUMMARYRECORD_CRAWLDETAILS_ARRAY_PROPERTY); if (crawlStatsArray != null) { for (JsonElement crawlDetail : crawlStatsArray.getAsJsonArray()) { // add to our list of crawl detail records ... safeAddCrawlDetailToSummaryRecord(crawlDetail.getAsJsonObject()); // ok, now update summary stats based on incoming crawl detail record ... updateSummaryRecordFromCrawlDetailRecord(crawlDetail.getAsJsonObject(),_currentKey,reporter); } } } } } /** * for the current url, merge the currently accumulated information with a previously generated crawl summary record * @param jsonObject * @param destFP * @param reporter * @throws IOException */ void processMergedRecord(JsonObject jsonObject,URLFPV2 destFP,Reporter reporter)throws IOException { if (jsonObject.has(TOPLEVEL_BLEKKO_METADATA_PROPERTY)) { reporter.incrCounter(Counters.MERGE_RECORD_HAS_BLEKKO_METADATA, 1); } if (_topLevelJSONObject == null) { reporter.incrCounter(Counters.MERGED_OBJECT_FIRST_OBJECT, 1); _topLevelJSONObject = jsonObject; _summaryRecord = jsonObject.getAsJsonObject(TOPLEVEL_SUMMARYRECORD_PROPRETY); _linkSummaryRecord = jsonObject.getAsJsonObject(TOPLEVEL_LINKSTATUS_PROPERTY); if (_linkSummaryRecord != null) { // read in type and rels collection ... safeJsonArrayToStringCollection(_linkSummaryRecord,LINKSTATUS_TYPEANDRELS_PROPERTY, _types); } // and ext hrefs .. if (_summaryRecord != null) { safeJsonArrayToStringCollection(_summaryRecord, SUMMARYRECORD_EXTERNALLY_REFERENCED_URLS,_extHrefs); } // special blekko import stats if (_topLevelJSONObject.has(TOPLEVEL_BLEKKO_METADATA_PROPERTY)) { if (_summaryRecord == null && _linkSummaryRecord == null) { reporter.incrCounter(Counters.BLEKKO_METADATA_WITH_NO_SOURCE_CC_RECORD, 1); } } } else { mergeSummaryRecords(jsonObject.getAsJsonObject(TOPLEVEL_SUMMARYRECORD_PROPRETY),_topLevelJSONObject,reporter); mergeLinkRecords(jsonObject.getAsJsonObject(TOPLEVEL_LINKSTATUS_PROPERTY),_topLevelJSONObject,reporter); mergeBlekkoMetadata(jsonObject.getAsJsonObject(TOPLEVEL_BLEKKO_METADATA_PROPERTY),_topLevelJSONObject,reporter); } } /** * given a incoming link record, track the link source and also update stats and * also capture document type information (if available via the href). * * @param jsonObject * @param destFP * @param reporter * @throws IOException */ void updateLinkStatsFromLinkJSONObject(JsonObject jsonObject,URLFPV2 destFP,Reporter reporter) throws IOException { JsonElement sourceElement = jsonObject.get("source_url"); JsonElement hrefElement = jsonObject.get("href"); if (sourceElement != null && hrefElement != null) { //LOG.info("source:" + sourceElement.getAsString() + " href:" + hrefElement.getAsString()); GoogleURL sourceURLObj = new GoogleURL(sourceElement.getAsString()); if (sourceURLObj.isValid()) { if (_linkSummaryRecord == null) { _linkSummaryRecord = new JsonObject(); } // ok, first compare known host name with incoming link host name ... // if not a match then ... if (!_outputKeyURLObj.getHost().equals(sourceURLObj.getHost())) { // ok now deeper check ... URLFPV2 sourceFP = URLUtils.getURLFPV2FromURLObject(sourceURLObj); if (sourceFP != null) { reporter.incrCounter(Counters.GOT_EXTERNAL_DOMAIN_SOURCE, 1); // increment external source count safeIncrementJSONCounter(_linkSummaryRecord,LINKSTATUS_EXTRADOMAIN_SOURCES_COUNT_PROPERTY); //LOG.info("sourceFP:" + sourceFP.getKey() + " hrefFP:" + destFP.getKey()); // ok track sources if from a different root domain (for now) if (sourceFP.getRootDomainHash() != destFP.getRootDomainHash()) { trackPotentialLinkSource(sourceFP,sourceElement.getAsString(),destFP); } } } // otherwise, count it as an internal link else { // internal for sure ... safeIncrementJSONCounter(_linkSummaryRecord,LINKSTATUS_INTRADOMAIN_SOURCES_COUNT_PROPERTY); } JsonObject sourceHeaders = jsonObject.getAsJsonObject("source_headers"); if (sourceHeaders != null) { long httpDate = safeGetHttpDate(sourceHeaders, "date"); long lastModified = safeGetHttpDate(sourceHeaders, "last-modified"); if (lastModified != -1 && lastModified < httpDate) httpDate = lastModified; if (httpDate != -1L) { safeSetMinLongValue(_linkSummaryRecord, LINKSTATUS_EARLIEST_DATE_PROPERTY, httpDate); safeSetMaxLongValue(_linkSummaryRecord, LINKSTATUS_LATEST_DATE_PROPERTY, httpDate); } } JsonElement typeElement = jsonObject.get("type"); JsonElement relElement = jsonObject.get("rel"); String sourceTypeAndRel = jsonObject.get("source_type").getAsString() + ":"; if (typeElement != null) { sourceTypeAndRel += typeElement.getAsString(); } if (relElement != null) { sourceTypeAndRel += ":" + relElement.getAsString(); } if (_types.size() < MAX_TYPE_SAMPLES) _types.add(sourceTypeAndRel); } } } /** * take linking href data and add it to our list of incoming hrefs * (used during the intermediate merge process) * * @param destFP * @param inputData * @throws IOException */ void importLinkSourceData(URLFPV2 destFP,TextBytes inputData) throws IOException { TextBytes urlText = new TextBytes(); int curpos =inputData.getOffset(); int endpos = inputData.getOffset() + inputData.getLength(); byte lfPattern[] = { 0xA }; byte tabPattern[] = { 0x9 }; while (curpos != endpos) { int tabIndex = ByteArrayUtils.indexOf(inputData.getBytes(), curpos, endpos - curpos, tabPattern); if (tabIndex == -1) { break; } else { int lfIndex = ByteArrayUtils.indexOf(inputData.getBytes(), tabIndex + 1, endpos - (tabIndex + 1), lfPattern); if (lfIndex == -1) { break; } else { long sourceDomainHash = ByteArrayUtils.parseLong(inputData.getBytes(),curpos, tabIndex-curpos, 10); urlText.set(inputData.getBytes(),tabIndex + 1,lfIndex - (tabIndex + 1)); URLFPV2 bloomKey = sourceKeyFromSourceAndDest(sourceDomainHash,destFP.getUrlHash()); if (!_sourceInputsTrackingFilter.isPresent(bloomKey)) { // if not, check to see that we are not about to overflow sample buffer ... if (_sourceInputsBuffer.getLength() < EXT_SOURCE_SAMPLE_BUFFER_SIZE - EXT_SOURCE_SAMPLE_BUFFER_PAD_AMOUNT) { _sourceInputsBuffer.write(inputData.getBytes(),curpos,(lfIndex + 1) - curpos); _sourceSampleSize++; } } curpos = lfIndex + 1; } } } } /** * given an incoming link for a given url, store it in a accumulation buffer IFF we have not * seen a url from the given domain before * * @param sourceFP * @param sourceURL * @param destFP * @throws IOException */ void trackPotentialLinkSource(URLFPV2 sourceFP,String sourceURL,URLFPV2 destFP) throws IOException { URLFPV2 bloomKey = sourceKeyFromSourceAndDest(sourceFP.getDomainHash(),destFP.getUrlHash()); // check to see if we have collected a sample for this source domain / destination url combo or not ... if (!_sourceInputsTrackingFilter.isPresent(bloomKey)) { LOG.debug("sourceFP:" + sourceFP.getKey() + " passed BloomFilter Test"); // if not, check to see that we are not about to overflow sample buffer ... if (_sourceInputsBuffer.getLength() < EXT_SOURCE_SAMPLE_BUFFER_SIZE - EXT_SOURCE_SAMPLE_BUFFER_PAD_AMOUNT) { // ok store the external reference sample ... // write source domain hash _sourceInputsBuffer.write(Long.toString(sourceFP.getDomainHash()).getBytes()); // delimiter _sourceInputsBuffer.write(0x09);// TAB // and source url ... _sourceInputsBuffer.write(sourceURL.getBytes(Charset.forName("UTF-8"))); _sourceInputsBuffer.write(0x0A);// LF _sourceSampleSize++; // add to bloom filter ... _sourceInputsTrackingFilter.add(bloomKey); } } else { LOG.debug("sourceFP:" + sourceFP.getKey() + " failed BloomFilter Test"); } } /** * construct a (hacked) fingerprint key consisting of the source domain and destination * url fingerprint to be used for the purposes of setting bits in a bloomfilter * * @param sourceDomain * @param destURLHash * @return */ private URLFPV2 sourceKeyFromSourceAndDest(long sourceDomain,long destURLHash) { _bloomFilterKey.setDomainHash(sourceDomain); _bloomFilterKey.setUrlHash(destURLHash); return _bloomFilterKey; } /** * construct crawl detail record from incoming crawl status JSON * * @param jsonObject * @param fpSource * @param extHRefs * @param reporter * @return * @throws IOException */ static JsonObject crawlDetailRecordFromCrawlStatusRecord(JsonObject jsonObject,URLFPV2 fpSource,HashSet<String> extHRefs,Reporter reporter)throws IOException { String disposition = jsonObject.get("disposition").getAsString(); long attemptTime = jsonObject.get("attempt_time").getAsLong(); // inject all the details into a JSONObject JsonObject crawlStatsJSON = new JsonObject(); crawlStatsJSON.addProperty(CRAWLDETAIL_ATTEMPT_TIME_PROPERTY, attemptTime); if (disposition.equals("SUCCESS")) { // basic stats ... starting with crawl time ... int httpResult = jsonObject.get("http_result").getAsInt(); crawlStatsJSON.addProperty(CRAWLDETAIL_HTTPRESULT_PROPERTY,httpResult); crawlStatsJSON.addProperty(CRAWLDETAIL_SERVERIP_PROPERTY, jsonObject.get("server_ip").getAsString()); //populate date headers ... populateDateHeadersFromJSONObject(jsonObject,crawlStatsJSON); // if http 200 ... if (httpResult >= 200 && httpResult <= 299) { reporter.incrCounter(Counters.GOT_HTTP_200_CRAWL_STATUS,1); crawlStatsJSON.addProperty(CRAWLDETAIL_CONTENTLEN_PROPERTY,jsonObject.get("content_len").getAsInt()); if (jsonObject.get("mime_type") != null) { crawlStatsJSON.addProperty(CRAWLDETAIL_MIMETYPE_PROPERTY,jsonObject.get("mime_type").getAsString()); } if (jsonObject.get("md5") != null) { crawlStatsJSON.addProperty(CRAWLDETAIL_MD5_PROPERTY,jsonObject.get("md5").getAsString()); } if (jsonObject.get("text_simhash") != null) { crawlStatsJSON.addProperty(CRAWLDETAIL_TEXTSIMHASH_PROPERTY,jsonObject.get("text_simhash").getAsLong()); } JsonElement parsedAs = jsonObject.get("parsed_as"); if (parsedAs != null) { // populate some info based on type ... crawlStatsJSON.addProperty(CRAWLDETAIL_PARSEDAS_PROPERTY,parsedAs.getAsString()); String parsedAsString = parsedAs.getAsString(); // if html ... if (parsedAsString.equals("html")) { JsonObject content = jsonObject.get("content").getAsJsonObject(); if (content != null) { JsonElement titleElement = content.get("title"); JsonElement metaElement = content.get("meta_tags"); if (titleElement != null) { crawlStatsJSON.add(CRAWLDETAIL_TITLE_PROPERTY, titleElement); } if (metaElement != null) { crawlStatsJSON.add(CRAWLDETAIL_METATAGS_PROPERTY, metaElement); } // collect link stats for json ... updateLinkStatsFromHTMLContent(crawlStatsJSON,jsonObject,extHRefs,fpSource,reporter); } } // if feed ... else if (parsedAsString.equals("feed")) { // get content ... JsonObject content = jsonObject.get("content").getAsJsonObject(); JsonElement titleElement = content.get("title"); if (titleElement != null) { crawlStatsJSON.add(CRAWLDETAIL_TITLE_PROPERTY, titleElement); } // set update time ... long updateTime = safeGetLong(content, "updated"); if (updateTime != -1) { crawlStatsJSON.addProperty(CRAWLDETAIL_UPDATED_PROPERTY, updateTime); } addMinMaxFeedItemTimes(content,crawlStatsJSON); } } } // redirect ... else if (httpResult >=300 && httpResult <= 399) { reporter.incrCounter(Counters.GOT_REDIRECT_CRAWL_STATUS,1); // get the target url ... JsonElement targetURL = jsonObject.get("target_url"); if (targetURL != null) { // redirect details ... crawlStatsJSON.addProperty(CRAWLDETAIL_REDIRECT_URL, targetURL.getAsString()); } else { reporter.incrCounter(Counters.GOT_NULL_REDIRECT_URL, 1); } } } else { // inject all the details into a JSONObject // basic stats ... starting with crawl time ... crawlStatsJSON.addProperty(CRAWLDETAIL_FAILURE,true); crawlStatsJSON.addProperty(CRAWLDETAIL_FAILURE_REASON,safeGetStringFromElement(jsonObject,"failure_reason")); crawlStatsJSON.addProperty(CRAWLDETAIL_FAILURE_DETAIL,safeGetStringFromElement(jsonObject,"failure_detail")); } return crawlStatsJSON; } /** * given a crawl detail json record, update summary record stats * * @param crawlDetailRecord * @param fpSource * @param reporter * @throws IOException */ void updateSummaryRecordFromCrawlDetailRecord(JsonObject crawlDetailRecord,URLFPV2 fpSource,Reporter reporter) throws IOException { if (_summaryRecord == null) { _summaryRecord = new JsonObject(); } boolean failure = safeGetBoolean(crawlDetailRecord,CRAWLDETAIL_FAILURE); long attemptTime = crawlDetailRecord.get(CRAWLDETAIL_ATTEMPT_TIME_PROPERTY).getAsLong(); // set latest attempt time ... long latestAttemptTime = safeSetMaxLongValue(_summaryRecord,SUMMARYRECORD_LATEST_ATTEMPT_PROPERTY,attemptTime); // increment attempt count safeIncrementJSONCounter(_summaryRecord,SUMMARYRECORD_ATTEMPT_COUNT_PROPERTY); // if this is the latest attempt ... if (latestAttemptTime == attemptTime) { // add latest http result to summary if (!failure && crawlDetailRecord.has(CRAWLDETAIL_HTTPRESULT_PROPERTY)) { int httpResult = crawlDetailRecord.get(CRAWLDETAIL_HTTPRESULT_PROPERTY).getAsInt(); // set last http result _summaryRecord.addProperty(SUMMARYRECORD_HTTP_RESULT_PROPERTY,httpResult); if (httpResult >= 200 && httpResult <= 299) { // update the crawl timestamp _summaryRecord.addProperty(SUMMARYRECORD_LATEST_CRAWLTIME_PROPERTY,attemptTime); // and the crawl count .... safeIncrementJSONCounter(_summaryRecord,SUMMARYRECORD_CRAWLCOUNT_PROPERTY); // update parsed as if (crawlDetailRecord.has(CRAWLDETAIL_PARSEDAS_PROPERTY)) { _summaryRecord.addProperty(SUMMARYRECORD_PARSEDAS_PROPERTY, safeGetStringFromElement(crawlDetailRecord,CRAWLDETAIL_PARSEDAS_PROPERTY)); } } else if (httpResult >=300 && httpResult <= 399) { if (crawlDetailRecord.has(CRAWLDETAIL_REDIRECT_URL)) { _summaryRecord.addProperty(SUMMARYRECORD_REDIRECT_URL_PROPERTY, safeGetStringFromElement(crawlDetailRecord,CRAWLDETAIL_REDIRECT_URL)); } } } } } /** * given html content (json object), extract out of domain hrefs and cache them * and ... update stats * @param crawlStats * @param incomingJSONObject * @param extHRefs * @param fpSource * @param reporter */ static void updateLinkStatsFromHTMLContent(JsonObject crawlStats,JsonObject incomingJSONObject,HashSet<String> extHRefs,URLFPV2 fpSource,Reporter reporter) { JsonArray links = incomingJSONObject.getAsJsonArray("links"); if (links == null) { reporter.incrCounter(Counters.NULL_LINKS_ARRAY, 1); } else { // clear our snapshot of externally referenced urls // we only want to capture this information from // the links extracted via the latest content if (extHRefs != null) extHRefs.clear(); int intraDomainLinkCount = 0; int intraRootLinkCount = 0; int interDomainLinkCount = 0; for (JsonElement link : links) { JsonObject linkObj = link.getAsJsonObject(); if (linkObj != null && linkObj.has("href")) { String href = linkObj.get("href").getAsString(); GoogleURL urlObject = new GoogleURL(href); if (urlObject.isValid()) { URLFPV2 linkFP = URLUtils.getURLFPV2FromURLObject(urlObject); if (linkFP != null) { if (linkFP.getRootDomainHash() == fpSource.getRootDomainHash()) { if (linkFP.getDomainHash() == fpSource.getDomainHash()) { intraDomainLinkCount ++; } else { intraRootLinkCount ++; } } else { interDomainLinkCount++; // track domains we link to if (extHRefs != null) { if (extHRefs.size() <= MAX_EXTERNALLY_REFERENCED_URLS) { extHRefs.add(urlObject.getCanonicalURL()); } } } } } } } // update counts in crawl stats data structure ... crawlStats.addProperty(CRAWLDETAIL_INTRADOMAIN_LINKS, intraDomainLinkCount); crawlStats.addProperty(CRAWLDETAIL_INTRAROOT_LINKS, intraRootLinkCount); crawlStats.addProperty(CRAWLDETAIL_INTERDOMAIN_LINKS, interDomainLinkCount); if (interDomainLinkCount <= 100) { reporter.incrCounter(Counters.INTERDOMAIN_LINKS_LTEQ_100, 1); } else if (interDomainLinkCount <= 1000) { reporter.incrCounter(Counters.INTERDOMAIN_LINKS_LTEQ_1000, 1); } else { reporter.incrCounter(Counters.INTERDOMAIN_LINKS_GT_1000, 1); } } } /** * flush currently accumulated JSON record * * @param output * @param reporter * @throws IOException */ private void flushCurrentRecord(OutputCollector<TextBytes, TextBytes> output, Reporter reporter)throws IOException { _urlsProcessed++; if (_outputKeyString == null || !_outputKeyURLObj.isValid()) { if (reporter != null) { reporter.incrCounter(Counters.FAILED_TO_GET_SOURCE_HREF, 1); } } else { if (_topLevelJSONObject != null || _summaryRecord != null || _linkSummaryRecord != null) { if (_topLevelJSONObject == null) { reporter.incrCounter(Counters.ALLOCATED_TOP_LEVEL_OBJECT_IN_FLUSH, 1); _topLevelJSONObject = new JsonObject(); _topLevelJSONObject.addProperty(TOPLEVEL_SOURCE_URL_PROPRETY,_outputKeyString); } else { reporter.incrCounter(Counters.ENCOUNTERED_EXISTING_TOP_LEVEL_OBJECT_IN_FLUSH, 1); } if (_summaryRecord != null) { _summaryRecord.remove(SUMMARYRECORD_EXTERNALLY_REFERENCED_URLS); _summaryRecord.remove(SUMMARYRECORD_EXTERNALLY_REFERENCED_URLS_TRUNCATED); if (_extHrefs.size() != 0) { // output links in the top level object ... stringCollectionToJsonArrayWithMax(_summaryRecord, SUMMARYRECORD_EXTERNALLY_REFERENCED_URLS, _extHrefs,MAX_EXTERNALLY_REFERENCED_URLS); if (_extHrefs.size() > MAX_EXTERNALLY_REFERENCED_URLS) { _summaryRecord.addProperty(SUMMARYRECORD_EXTERNALLY_REFERENCED_URLS_TRUNCATED,true); } } reporter.incrCounter(Counters.ENCOUNTERED_SUMMARY_RECORD_IN_FLUSH, 1); _topLevelJSONObject.add(TOPLEVEL_SUMMARYRECORD_PROPRETY, _summaryRecord); } if (_linkSummaryRecord != null) { reporter.incrCounter(Counters.ENCOUNTERED_LINKSUMMARY_RECORD_IN_FLUSH, 1); if (_types != null && _types.size() != 0) { stringCollectionToJsonArray(_linkSummaryRecord,LINKSTATUS_TYPEANDRELS_PROPERTY,_types); } _topLevelJSONObject.add(TOPLEVEL_LINKSTATUS_PROPERTY, _linkSummaryRecord); } //System.out.println("Emitting Key:" + CrawlDBKey.generateKey(_currentKey, CrawlDBKey.Type.KEY_TYPE_MERGED_RECORD, 0)); if (_topLevelJSONObject.has(TOPLEVEL_BLEKKO_METADATA_PROPERTY)) { JsonObject blekkoMetadata = _topLevelJSONObject.getAsJsonObject(TOPLEVEL_BLEKKO_METADATA_PROPERTY); reporter.incrCounter(Counters.EMITTED_RECORD_WITH_BLEKKO_METADATA, 1); if (_linkSummaryRecord != null || _summaryRecord != null ) { reporter.incrCounter(Counters.BLEKKO_RECORD_ALREADY_IN_DATABASE, 1); if (_summaryRecord != null) { if (_summaryRecord.has(SUMMARYRECORD_ATTEMPT_COUNT_PROPERTY) && _summaryRecord.get(SUMMARYRECORD_ATTEMPT_COUNT_PROPERTY).getAsInt() != 0) { String status = blekkoMetadata.get(BLEKKO_METADATA_STATUS).getAsString(); if (status.equalsIgnoreCase("crawled")) { reporter.incrCounter(Counters.BLEKKO_CRAWLED_CC_CRAWLED, 1); } else { reporter.incrCounter(Counters.BLEKKO_NOT_CRAWLED_CC_CRAWLED, 1); } } } } } // output top level record ... output.collect(CrawlDBKey.generateKey(_currentKey, CrawlDBKey.Type.KEY_TYPE_MERGED_RECORD, 0),new TextBytes(_topLevelJSONObject.toString())); // if there is link status available ... if (_sourceSampleSize != 0) { reporter.incrCounter(Counters.EMITTED_SOURCEINPUTS_RECORD, 1); TextBytes sourceInputsText= new TextBytes(); sourceInputsText.set(_sourceInputsBuffer.getData(),0,_sourceInputsBuffer.getLength()); //System.out.println("Emitting Key:" + CrawlDBKey.generateKey(_currentKey, CrawlDBKey.Type.KEY_TYPE_INCOMING_URLS_SAMPLE, 0)); output.collect(CrawlDBKey.generateKey(_currentKey, CrawlDBKey.Type.KEY_TYPE_INCOMING_URLS_SAMPLE, 0),sourceInputsText); reporter.incrCounter(Counters.EMITTED_SOURCEINPUTS_DATA_BYTES_EMITTED, sourceInputsText.getLength()); } } if (_urlsProcessed % FLUSH_INTERVAL == 0) { _sourceInputsTrackingFilter.clear(); } } _sourceInputsBuffer.reset(); _sourceSampleSize = 0; _topLevelJSONObject = null; _summaryRecord = null; _linkSummaryRecord = null; _types.clear(); _extHrefs.clear(); _outputKeyString = null; _urlKeyForzen = false; _outputKeyURLObj = null; } /** * Extract the fingerprint from the incoming key and potentially trigger a flush if it is indicative of a * primary key transition * @param key * @param output * @param reporter * @throws IOException */ private void readFPCheckForTransition(TextBytes key,OutputCollector<TextBytes, TextBytes> output, Reporter reporter)throws IOException { if (_tempKey == null) { _tempKey = new URLFPV2(); } _tempKey.setRootDomainHash(CrawlDBKey.getLongComponentFromKey(key, ComponentId.ROOT_DOMAIN_HASH_COMPONENT_ID)); _tempKey.setDomainHash(CrawlDBKey.getLongComponentFromKey(key, ComponentId.DOMAIN_HASH_COMPONENT_ID)); _tempKey.setUrlHash(CrawlDBKey.getLongComponentFromKey(key, ComponentId.URL_HASH_COMPONENT_ID)); if (_currentKey == null) { _currentKey = _tempKey; _tempKey = null; } else { // check for key transition ... if (_currentKey.compareTo(_tempKey) != 0) { // transition flushCurrentRecord(output,reporter); // swap keys ... URLFPV2 oldKey = _currentKey; _currentKey = _tempKey; _tempKey = oldKey; } } } /** * add crawl detail to summary record. construct a summary detail if none exists ... * * @param crawlStatsJSON */ void safeAddCrawlDetailToSummaryRecord(JsonObject crawlStatsJSON) { if (_summaryRecord == null) { _summaryRecord = new JsonObject(); } // construct crawl stats array if necessary JsonArray crawlStatsArray = _summaryRecord.getAsJsonArray(SUMMARYRECORD_CRAWLDETAILS_ARRAY_PROPERTY); if (crawlStatsArray == null) { crawlStatsArray = new JsonArray(); _summaryRecord.add(SUMMARYRECORD_CRAWLDETAILS_ARRAY_PROPERTY, crawlStatsArray); } // add crawl stats to it crawlStatsArray.add(crawlStatsJSON); } /** * scan the merge db path and find the latest crawl database timestamp * * @param fs * @param conf * @return * @throws IOException */ static long findLatestMergeDBTimestamp(FileSystem fs,Configuration conf)throws IOException { long timestampOut = -1L; FileStatus files[] = fs.globStatus(new Path(S3N_BUCKET_PREFIX + MERGE_DB_PATH,"[0-9]*")); for (FileStatus candidate : files) { Path successPath = new Path(candidate.getPath(),"_SUCCESS"); if (fs.exists(successPath)) { long timestamp = Long.parseLong(candidate.getPath().getName()); timestampOut = Math.max(timestamp, timestampOut); } } return timestampOut; } /** * iterate the intermediate link graph data and extract unmerged set ... * * @param fs * @param conf * @param latestMergeDBTimestamp * @return * @throws IOException */ static List<Path> filterMergeCandidtes(FileSystem fs,Configuration conf, long latestMergeDBTimestamp )throws IOException { ArrayList<Path> list = new ArrayList<Path>(); FileStatus candidates[] = fs.globStatus(new Path(S3N_BUCKET_PREFIX + MERGE_INTERMEDIATE_OUTPUT_PATH,"[0-9]*")); for (FileStatus candidate : candidates) { LOG.info("Found Merge Candidate:" + candidate.getPath()); long candidateTimestamp = Long.parseLong(candidate.getPath().getName()); if (candidateTimestamp > latestMergeDBTimestamp) { Path successPath = new Path(candidate.getPath(),"_SUCCESS"); if (fs.exists(successPath)) { list.add(candidate.getPath()); } else { LOG.info("Rejected Merge Candidate:" + candidate.getPath()); } } } return list; } /////////////////////////////////////////////////////////////////////////// // TEST CODE /////////////////////////////////////////////////////////////////////////// /* // PARK THIS CODE FOR NOW SINCE WE ARE TRANSFERRING DATA PROCESSING TO EC2 if (_skipPartition) return; // collect all incoming paths first Vector<Path> incomingPaths = new Vector<Path>(); while(values.hasNext()){ String path = values.next().toString(); LOG.info("Found Incoming Path:" + path); incomingPaths.add(new Path(path)); } FlexBuffer scanArray[] = LinkKey.allocateScanArray(); // set up merge attributes Configuration localMergeConfig = new Configuration(_conf); localMergeConfig.setClass( MultiFileInputReader.MULTIFILE_COMPARATOR_CLASS, LinkKeyGroupingComparator.class, RawComparator.class); localMergeConfig.setClass(MultiFileInputReader.MULTIFILE_KEY_CLASS, TextBytes.class, WritableComparable.class); // ok now spawn merger MultiFileInputReader<TextBytes> multiFileInputReader = new MultiFileInputReader<TextBytes>( _fs, incomingPaths, localMergeConfig); TextBytes keyBytes = new TextBytes(); TextBytes valueBytes = new TextBytes(); DataInputBuffer inputBuffer = new DataInputBuffer(); int processedKeysCount = 0; Pair<KeyAndValueData<TextBytes>,Iterable<RawRecordValue>> nextItem = null; while ((nextItem = multiFileInputReader.getNextItemIterator()) != null) { urlsProcessed++; _sourceInputsBuffer.reset(); _sourceSampleSize = 0; summaryRecord = null; linkSummaryRecord = null; types.clear(); outputKeyString = null; outputKeyFromInternalLink = false; outputKeyURLObj = null; extLinkedDomains.clear(); int statusCount = 0; int linkCount = 0; // scan key components LinkKey.scanForComponents(nextItem.e0._keyObject, ':',scanArray); // pick up source fp from key ... URLFPV2 fpSource = new URLFPV2(); fpSource.setRootDomainHash(LinkKey.getLongComponentFromComponentArray(scanArray,LinkKey.ComponentId.ROOT_DOMAIN_HASH_COMPONENT_ID)); fpSource.setDomainHash(LinkKey.getLongComponentFromComponentArray(scanArray,LinkKey.ComponentId.DOMAIN_HASH_COMPONENT_ID)); fpSource.setUrlHash(LinkKey.getLongComponentFromComponentArray(scanArray,LinkKey.ComponentId.URL_HASH_COMPONENT_ID)); for (RawRecordValue rawValue: nextItem.e1) { inputBuffer.reset(rawValue.key.getData(),0,rawValue.key.getLength()); int length = WritableUtils.readVInt(inputBuffer); keyBytes.set(rawValue.key.getData(),inputBuffer.getPosition(),length); inputBuffer.reset(rawValue.data.getData(),0,rawValue.data.getLength()); length = WritableUtils.readVInt(inputBuffer); valueBytes.set(rawValue.data.getData(),inputBuffer.getPosition(),length); */ }