Bladeren bron

HPCC-10941 Add mechanism for finding queries that use a given file

Build a cache of files used by queries.  Update cache when DALI changes
in QuerySets, PackageMaps, or PackageSets are detected.

Add method to WsWorkunits to WUListQueriesUsingFile.

Create a report xslt for the result, and add a link to the the
context menu for roxie files listed on the browse dfu files page.

Signed-off-by: Anthony Fishbeck <anthony.fishbeck@lexisnexis.com>
Anthony Fishbeck 11 jaren geleden
bovenliggende
commit
d5a577dad5

+ 1 - 1
common/workunit/referencedfilelist.cpp

@@ -586,7 +586,7 @@ void ReferencedFileList::addFilesFromQuery(IConstWorkUnit *cw, const IHpccPackag
                         {
                             StringBuffer subfile;
                             ssfe->getSubFileName(count, subfile);
-                            ensureFile(subfile, RefSubFile | RefFileInPackage, NULL);
+                            ensureFile(subfile, RefSubFile | RefFileInPackage, pkgid);
                         }
                     }
                 }

+ 86 - 0
esp/eclwatch/ws_XSLT/QueriesUsingFile.xslt

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    HPCC SYSTEMS software Copyright (C) 2014 HPCC Systems.
+
+    This program is free software: you can redistribute it and/or modify
+    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.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html"/>
+    <xsl:template match="/WUListQueriesUsingFileResponse">
+        <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+            <head>
+        <xsl:text disable-output-escaping="yes"><![CDATA[
+          <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/fonts/fonts-min.css" />
+          <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/menu/assets/skins/sam/menu.css" />
+          <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/button/assets/skins/sam/button.css" />
+          <link rel="stylesheet" type="text/css" href="/esp/files/css/espdefault.css" />
+          <link rel="stylesheet" type="text/css" href="/esp/files/css/eclwatch.css" />
+          <link type="text/css" rel="styleSheet" href="/esp/files/css/sortabletable.css"/>
+          <script type="text/javascript" src="/esp/files/scripts/espdefault.js"></script>
+          <script type="text/javascript" src="/esp/files/yui/build/yahoo-dom-event/yahoo-dom-event.js"></script>
+        ]]></xsl:text>
+
+            </head>
+            <body onload="nof5();" class="yui-skin-sam">
+                <h3>Queries using file:</h3><br/>
+                <b>File: </b><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="FileName"/><br/>
+                <xsl:if test="Process">
+                    <b>On Cluster: </b><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="Process"/><br/>
+                </xsl:if>
+                <xsl:apply-templates select="Targets/TargetQueriesUsingFile"/>
+                <xsl:if test="count(Targets/TargetQueriesUsingFile/Queries/QueryUsingFile)=0">
+                    <p>The are no Queries using this file.</p>
+                </xsl:if>
+            </body>
+        </html>
+    </xsl:template>
+
+  <xsl:template match="TargetQueriesUsingFile">
+    <b>Target: </b><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="Target"/><br/>
+    <xsl:if test="PackageMap">
+        <b>PackageMap: </b><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="PackageMap"/><br/>
+    </xsl:if>
+    <br/>
+    <xsl:apply-templates select="Queries"/><br/><br/>
+  </xsl:template>
+
+  <xsl:template match="Queries">
+    <table class="sort-table" id="resultsTable">
+      <thead>
+        <tr class="grey">
+          <th>Query ID</th>
+          <th>Package</th>
+        </tr>
+      </thead>
+      <tbody>
+        <xsl:apply-templates select="QueryUsingFile"/>
+      </tbody>
+    </table>
+  </xsl:template>
+
+  <xsl:template match="QueryUsingFile">
+    <tr>
+      <td>
+        <a href="/WsWorkunits/WUQueryDetails?IncludeSuperFiles=1&amp;IncludeStateOnClusters=1&amp;QueryId={Id}&amp;QuerySet={../../Target}"><xsl:value-of select="Id"/>
+        </a>
+      </td>
+      <td>
+        <xsl:value-of select="Package"/>
+      </td>
+    </tr>
+  </xsl:template>
+
+  <xsl:template match="*|@*|text()"/>
+</xsl:stylesheet>

+ 10 - 0
esp/eclwatch/ws_XSLT/dfu.xslt

@@ -84,6 +84,10 @@
             function detailsDFUFile() {
               document.location.href='/WsDfu/DFUInfo?Name='+ escape(filename) + '&Cluster=' + cluster;
                       }
+            function queriesUsingDFUFile() {
+              document.location.href='/WsWorkunits/WUListQueriesUsingFile?FileName='+ escape(filename) + '&Process=' + cluster;
+                      }
+
                         function browseDFUData() {
                           document.location.href='/WsDfu/DFUGetDataColumns?OpenLogicalName='+filename;
                         }
@@ -138,6 +142,12 @@
                 ]);
             }
 
+            if (roxiecluster != 0) {
+                oMenu.addItems([
+                    { text: "Used By", onclick: { fn: queriesUsingDFUFile } }
+                ]);
+            }
+
             //showPopup(menu,(window.event ? window.event.screenX : 0),  (window.event ? window.event.screenY : 0));
             oMenu.render("dfulogicalfilemenu");
             oMenu.show();

+ 29 - 0
esp/scm/ws_workunits.ecm

@@ -1252,6 +1252,34 @@ ESPresponse [exceptions_inline] WUListQueriesResponse
     ESParray<ESPstruct QuerySetQuery> QuerysetQueries;
 };
 
+ESPrequest [nil_remove] WUListQueriesUsingFileRequest
+{
+    string Target;
+    string Process;
+    string FileName;
+};
+
+ESPStruct [nil_remove] QueryUsingFile
+{
+    string Id;
+    string Package;
+};
+
+ESPStruct [nil_remove] TargetQueriesUsingFile
+{
+    string Target;
+    string PackageMap;
+    ESParray<ESPstruct QueryUsingFile> Queries;
+};
+
+
+ESPresponse [exceptions_inline] WUListQueriesUsingFileResponse
+{
+    string Process;
+    string FileName;
+    ESParray<ESPstruct TargetQueriesUsingFile> Targets;
+};
+
 ESPrequest WUQueryDetailsRequest
 {
     string QueryId;
@@ -1515,6 +1543,7 @@ ESPservice [
     ESPmethod [resp_xsl_default("/esp/xslt/WUCopyLogicalFiles.xslt")] WUCopyLogicalFiles(WUCopyLogicalFilesRequest, WUCopyLogicalFilesResponse);
     ESPmethod WUQueryConfig(WUQueryConfigRequest, WUQueryConfigResponse);
     ESPmethod WUListQueries(WUListQueriesRequest, WUListQueriesResponse);
+    ESPmethod [resp_xsl_default("/esp/xslt/QueriesUsingFile.xslt")] WUListQueriesUsingFile(WUListQueriesUsingFileRequest, WUListQueriesUsingFileResponse);
     ESPmethod WUCreateZAPInfo(WUCreateZAPInfoRequest, WUCreateZAPInfoResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/WUZAPInfoForm.xslt")] WUGetZAPInfo(WUGetZAPInfoRequest, WUGetZAPInfoResponse);
 };

+ 199 - 0
esp/services/ws_workunits/ws_workunitsQuerySets.cpp

@@ -225,6 +225,153 @@ void copyWULogicalFilesToTarget(IEspContext &context, IConstWUClusterInfo &clust
     }
 }
 
+void QueryFilesInUse::loadTarget(IPropertyTree *t, const char *target, unsigned flags)
+{
+    if (!target || !*target)
+        return;
+
+    Owned<IConstWUClusterInfo> clusterInfo = getTargetClusterInfo(target);
+    if (!clusterInfo || !(clusterInfo->getPlatform() == RoxieCluster))
+        return;
+
+    Owned<IPropertyTree> queryRegistry = getQueryRegistry(target, true);
+    if (!queryRegistry)
+        return;
+
+    SCMStringBuffer process;
+    clusterInfo->getRoxieProcess(process);
+    if (!process.length())
+        return;
+
+    Owned<IHpccPackageSet> ps = createPackageSet(process.str());
+    const IHpccPackageMap *pm = (ps) ? ps->queryActiveMap(target) : NULL;
+    const char *pmid = (pm) ? pm->queryPackageId() : NULL;
+
+    VStringBuffer xpath("%s/@pmid", target);
+    const char *pmidPrev = t->queryProp(xpath);
+    if ((flags & UFO_RELOAD_TARGETS_CHANGED_PMID) && (pmid || pmidPrev))
+    {
+        if (!(pmid && pmidPrev) || !streq(pmid, pmidPrev))
+            t->removeProp(target);
+    }
+    IPropertyTree *targetTree = ensurePTree(t, target);
+    if (pm)
+        targetTree->setProp("@pmid", pmid);
+
+    if (flags & UFO_REMOVE_QUERIES_NOT_IN_QUERYSET)
+    {
+        Owned<IPropertyTreeIterator> cachedQueries = targetTree->getElements("Query");
+        ForEach(*cachedQueries)
+        {
+            IPropertyTree &cachedQuery = cachedQueries->query();
+            VStringBuffer xpath("Query[@id='%s']", cachedQuery.queryProp("@id"));
+            if (!queryRegistry->hasProp(xpath))
+                targetTree->removeTree(&cachedQuery);
+        }
+    }
+
+    Owned<IPropertyTreeIterator> queries = queryRegistry->getElements("Query");
+    ForEach(*queries)
+    {
+        if (aborting)
+            return;
+        IPropertyTree &query = queries->query();
+        const char *queryid = query.queryProp("@id");
+        if (!queryid || !*queryid)
+            continue;
+        const char *wuid = query.queryProp("@wuid");
+        if (!wuid || !*wuid)
+            continue;
+
+        const char *pkgid=NULL;
+        if (pm)
+         {
+             const IHpccPackage *pkg = pm->matchPackage(queryid);
+             if (pkg)
+                 pkgid = pkg->queryId();
+         }
+        VStringBuffer xpath("Query[@id='%s']", queryid);
+        IPropertyTree *queryTree = targetTree->queryPropTree(xpath);
+        if (queryTree)
+        {
+            const char *cachedPkgid = queryTree->queryProp("@pkgid");
+            if (pkgid && *pkgid)
+            {
+                if (!(flags & UFO_RELOAD_MAPPED_QUERIES) && (cachedPkgid && streq(pkgid, cachedPkgid)))
+                    continue;
+            }
+            else if (!cachedPkgid || !*cachedPkgid)
+                continue;
+            targetTree->removeTree(queryTree);
+            queryTree = NULL;
+        }
+
+        Owned<IWorkUnitFactory> factory = getWorkUnitFactory();
+        Owned<IConstWorkUnit> cw = factory->openWorkUnit(wuid, false);
+        if (!cw)
+            continue;
+
+        queryTree = targetTree->addPropTree("Query", createPTree("Query"));
+        queryTree->setProp("@id", queryid);
+        if (pkgid && *pkgid)
+            queryTree->setProp("@pkgid", pkgid);
+
+        Owned<IReferencedFileList> wufiles = createReferencedFileList(NULL, NULL, true);
+        wufiles->addFilesFromQuery(cw, pm, queryid);
+        if (aborting)
+            return;
+        wufiles->resolveFiles(process.str(), NULL, NULL, NULL, true, true, false);
+
+        Owned<IReferencedFileIterator> files = wufiles->getFiles();
+        ForEach(*files)
+        {
+            if (aborting)
+                return;
+            IReferencedFile &rf = files->query();
+            if (!(rf.getFlags() & RefSubFile))
+                continue;
+            const char *lfn = rf.getLogicalName();
+            if (!lfn || !*lfn)
+                continue;
+
+            if (!queryTree->hasProp(xpath.setf("SubFile[@lfn='%s']", lfn)))
+            {
+                IPropertyTree *fileTree = queryTree->addPropTree("SubFile", createPTree("SubFile"));
+                fileTree->setProp("@lfn", lfn);
+                const char *fpkgid = rf.queryPackageId();
+                if (fpkgid && *fpkgid)
+                    fileTree->setProp("@pkgid", fpkgid);
+            }
+        }
+    }
+}
+
+void QueryFilesInUse::loadTargets(IPropertyTree *t, unsigned flags)
+{
+    Owned<IStringIterator> targets = getTargetClusters("RoxieCluster", NULL);
+    SCMStringBuffer s;
+    ForEach(*targets)
+    {
+        if (aborting)
+            return;
+        loadTarget(t, targets->str(s).str(), flags);
+    }
+}
+
+IPropertyTreeIterator *QueryFilesInUse::findQueriesUsingFile(const char *target, const char *lfn)
+{
+    CriticalBlock b(crit);
+
+    if (!target || !*target || !lfn || !*lfn)
+        return NULL;
+    IPropertyTree *targetTree = tree->getPropTree(target);
+    if (!targetTree)
+        return NULL;
+
+    VStringBuffer xpath("Query[SubFile/@lfn='%s']", lfn);
+    return targetTree->getElements(xpath);
+}
+
 bool CWsWorkunitsEx::onWUCopyLogicalFiles(IEspContext &context, IEspWUCopyLogicalFilesRequest &req, IEspWUCopyLogicalFilesResponse &resp)
 {
     StringBuffer wuid = req.getWuid();
@@ -1110,6 +1257,58 @@ bool CWsWorkunitsEx::onWUListQueries(IEspContext &context, IEspWUListQueriesRequ
     return true;
 }
 
+bool CWsWorkunitsEx::onWUListQueriesUsingFile(IEspContext &context, IEspWUListQueriesUsingFileRequest &req, IEspWUListQueriesUsingFileResponse &resp)
+{
+    const char *target = req.getTarget();
+    const char *process = req.getProcess();
+
+    StringBuffer lfn(req.getFileName());
+    resp.setFileName(lfn.toLowerCase());
+    resp.setProcess(process);
+
+    StringArray targets;
+    if (target && *target)
+        targets.append(target);
+    else // if (process && *process)
+    {
+        SCMStringBuffer targetStr;
+        Owned<IStringIterator> targetClusters = getTargetClusters("RoxieCluster", process);
+        ForEach(*targetClusters)
+            targets.append(targetClusters->str(targetStr).str());
+    }
+
+    IArrayOf<IEspTargetQueriesUsingFile> respTargets;
+    ForEachItemIn(i, targets)
+    {
+        target = targets.item(i);
+        Owned<IEspTargetQueriesUsingFile> respTarget = createTargetQueriesUsingFile();
+        respTarget->setTarget(target);
+        const char *pmid = filesInUse.getPackageMap(target);
+        if (pmid && *pmid)
+            respTarget->setPackageMap(pmid);
+
+        IPropertyTreeIterator *queries = filesInUse.findQueriesUsingFile(target, lfn);
+        IArrayOf<IEspQueryUsingFile> respQueries;
+        ForEach(*queries)
+        {
+            IPropertyTree &query = queries->query();
+            Owned<IEspQueryUsingFile> q = createQueryUsingFile();
+            q->setId(query.queryProp("@id"));
+
+            VStringBuffer xpath("SubFile[@lfn='%s']/@pkgid", lfn.str());
+            if (query.hasProp(xpath))
+                q->setPackage(query.queryProp(xpath));
+            respQueries.append(*q.getClear());
+        }
+        respTarget->setQueries(respQueries);
+        respTargets.append(*respTarget.getClear());
+    }
+    resp.setTargets(respTargets);
+
+    return true;
+}
+
+
 bool CWsWorkunitsEx::onWUQueryDetails(IEspContext &context, IEspWUQueryDetailsRequest & req, IEspWUQueryDetailsResponse & resp)
 {
     const char* querySet = req.getQuerySet();

+ 4 - 0
esp/services/ws_workunits/ws_workunitsService.cpp

@@ -720,6 +720,10 @@ void CWsWorkunitsEx::init(IPropertyTree *cfg, const char *process, const char *s
     recursiveCreateDirectory(ESP_WORKUNIT_DIR);
 
     m_sched.start();
+    filesInUse.subscribe();
+
+    QueryFilesInUseUpdateThread *updateFilesInUse = new QueryFilesInUseUpdateThread(filesInUse);
+    updateFilesInUse->startRelease();
 }
 
 void CWsWorkunitsEx::refreshValidClusters()

+ 117 - 2
esp/services/ws_workunits/ws_workunitsService.hpp

@@ -21,10 +21,119 @@
 #include "ws_workunits_esp.ipp"
 #include "workunit.hpp"
 #include "ws_workunitsHelpers.hpp"
+#include "dasds.hpp"
 #ifdef _USE_ZLIB
 #include "zcrypt.hpp"
 #endif
 
+#define UFO_RELOAD_TARGETS_CHANGED_PMID          0x01
+#define UFO_RELOAD_MAPPED_QUERIES                0x02
+#define UFO_REMOVE_QUERIES_NOT_IN_QUERYSET       0x04
+
+class QueryFilesInUse : public CInterface, implements ISDSSubscription
+{
+    mutable CriticalSection crit;
+    Owned<IPropertyTree> tree;
+    SubscriptionId qsChange;
+    SubscriptionId pmChange;
+    SubscriptionId psChange;
+    bool aborting;
+
+public:
+    IMPLEMENT_IINTERFACE;
+    QueryFilesInUse() : aborting(false), qsChange(0), pmChange(0), psChange(0)
+    {
+        tree.setown(createPTree("QueryFilesInUse"));
+    }
+
+    const char *getPackageMap(const char *target)
+    {
+        VStringBuffer xpath("%s/@pmid", target);
+        return tree->queryProp(xpath);
+    }
+
+    void loadTarget(IPropertyTree *tree, const char *target, unsigned flags);
+    void loadTargets(IPropertyTree *tree, unsigned flags);
+    void reload(unsigned flags)
+    {
+        Owned<IPropertyTree> t = createPTreeFromIPT(tree);
+        loadTargets(t, flags);
+        CriticalBlock b(crit);
+        tree.setown(t.getClear());
+    }
+
+    virtual void notify(SubscriptionId subid, const char *xpath, SDSNotifyFlags flags, unsigned valueLen, const void *valueData)
+    {
+        Linked<QueryFilesInUse> me = this;  // Ensure that I am not released by the notify call (which would then access freed memory to release the critsec)
+        if (subid == qsChange)
+            reload(UFO_REMOVE_QUERIES_NOT_IN_QUERYSET);
+        else if (subid == pmChange)
+            reload(UFO_RELOAD_MAPPED_QUERIES);
+        else if (subid == psChange)
+            reload(UFO_RELOAD_TARGETS_CHANGED_PMID);
+    }
+    virtual void subscribe()
+    {
+        CriticalBlock b(crit);
+        try
+        {
+            qsChange = querySDS().subscribe("QuerySets", *this, true);
+            pmChange = querySDS().subscribe("PackageMaps", *this, true);
+            psChange = querySDS().subscribe("PackageSets", *this, true);
+        }
+        catch (IException *E)
+        {
+            //TBD failure to subscribe implies dali is down...
+            E->Release();
+        }
+    }
+    virtual void unsubscribe()
+    {
+        CriticalBlock b(crit);
+        try
+        {
+            if (qsChange)
+                querySDS().unsubscribe(qsChange);
+            if (pmChange)
+                querySDS().unsubscribe(pmChange);
+            if (psChange)
+                querySDS().unsubscribe(psChange);
+        }
+        catch (IException *E)
+        {
+            E->Release();
+        }
+        qsChange = 0;
+        pmChange = 0;
+        psChange = 0;
+    }
+
+    void abort()
+    {
+        aborting=true;
+        CriticalBlock b(crit);
+    }
+    IPropertyTreeIterator *findQueriesUsingFile(const char *target, const char *lfn);
+};
+
+class QueryFilesInUseUpdateThread : public Thread
+{
+    QueryFilesInUse &filesInUse;
+
+public:
+    QueryFilesInUseUpdateThread(QueryFilesInUse &_filesInUse) : filesInUse(_filesInUse) {}
+
+    virtual int run()
+    {
+        filesInUse.reload(0);
+        return 0;
+    }
+    virtual void start()
+    {
+        Thread::start();
+    }
+};
+
 class CWsWorkunitsEx : public CWsWorkunits
 {
 public:
@@ -32,7 +141,11 @@ public:
 
     CWsWorkunitsEx(){port=8010;}
 
-    virtual ~CWsWorkunitsEx(){};
+    virtual ~CWsWorkunitsEx()
+    {
+        filesInUse.unsubscribe();
+        filesInUse.abort();
+    };
     virtual void init(IPropertyTree *cfg, const char *process, const char *service);
     virtual void setContainer(IEspContainer * container)
     {
@@ -59,6 +172,8 @@ public:
     bool onWUQuerysetCopyQuery(IEspContext &context, IEspWUQuerySetCopyQueryRequest &req, IEspWUQuerySetCopyQueryResponse &resp);
     bool onWUCopyLogicalFiles(IEspContext &context, IEspWUCopyLogicalFilesRequest &req, IEspWUCopyLogicalFilesResponse &resp);
     bool onWUQueryDetails(IEspContext &context, IEspWUQueryDetailsRequest & req, IEspWUQueryDetailsResponse & resp);
+    bool onWUListQueries(IEspContext &context, IEspWUListQueriesRequest &req, IEspWUListQueriesResponse &resp);
+    bool onWUListQueriesUsingFile(IEspContext &context, IEspWUListQueriesUsingFileRequest &req, IEspWUListQueriesUsingFileResponse &resp);
 
     bool onWUInfo(IEspContext &context, IEspWUInfoRequest &req, IEspWUInfoResponse &resp);
     bool onWUInfoDetails(IEspContext &context, IEspWUInfoRequest &req, IEspWUInfoResponse &resp);
@@ -114,7 +229,6 @@ public:
     void setPort(unsigned short _port){port=_port;}
 
     bool isQuerySuspended(const char* query, IConstWUClusterInfo *clusterInfo, unsigned wait, StringBuffer& errorMessage);
-    bool onWUListQueries(IEspContext &context, IEspWUListQueriesRequest &req, IEspWUListQueriesResponse &resp);
     bool onWUCreateZAPInfo(IEspContext &context, IEspWUCreateZAPInfoRequest &req, IEspWUCreateZAPInfoResponse &resp);
     bool onWUGetZAPInfo(IEspContext &context, IEspWUGetZAPInfoRequest &req, IEspWUGetZAPInfoResponse &resp);
 private:
@@ -132,6 +246,7 @@ private:
     WUSchedule m_sched;
     unsigned short port;
     Owned<IPropertyTree> directories;
+    QueryFilesInUse filesInUse;
 };
 
 class CWsWorkunitsSoapBindingEx : public CWsWorkunitsSoapBinding