mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
DS-2659 : introducing new extensible health check platform emailing reports on a regular basis
This commit is contained in:

committed by
Ondřej Košarko

parent
592207245c
commit
d2f9fb5104
@@ -71,6 +71,9 @@ public class LogAnalyser
|
||||
|
||||
/** warning counter */
|
||||
private static int warnCount = 0;
|
||||
|
||||
/** exception counter */
|
||||
private static int excCount = 0;
|
||||
|
||||
/** log line counter */
|
||||
private static int lineCount = 0;
|
||||
@@ -289,7 +292,7 @@ public class LogAnalyser
|
||||
* @param myEndDate the desired end of the analysis. Goes to the end otherwise
|
||||
* @param myLookUp force a lookup of the database
|
||||
*/
|
||||
public static void processLogs(Context context, String myLogDir,
|
||||
public static String processLogs(Context context, String myLogDir,
|
||||
String myFileTemplate, String myConfigFile,
|
||||
String myOutFile, Date myStartDate,
|
||||
Date myEndDate, boolean myLookUp)
|
||||
@@ -428,7 +431,12 @@ public class LogAnalyser
|
||||
// aggregator
|
||||
warnCount++;
|
||||
}
|
||||
|
||||
// count the exceptions
|
||||
if (logLine.isLevel("ERROR"))
|
||||
{
|
||||
excCount++;
|
||||
}
|
||||
|
||||
// is the action a search?
|
||||
if (logLine.isAction("search"))
|
||||
{
|
||||
@@ -513,9 +521,7 @@ public class LogAnalyser
|
||||
}
|
||||
|
||||
// finally, write the output
|
||||
createOutput();
|
||||
|
||||
return;
|
||||
return createOutput();
|
||||
}
|
||||
|
||||
|
||||
@@ -575,7 +581,7 @@ public class LogAnalyser
|
||||
/**
|
||||
* generate the analyser's output to the specified out file
|
||||
*/
|
||||
public static void createOutput()
|
||||
public static String createOutput()
|
||||
{
|
||||
// start a string buffer to hold the final output
|
||||
StringBuffer summary = new StringBuffer();
|
||||
@@ -588,6 +594,7 @@ public class LogAnalyser
|
||||
|
||||
// output the number of warnings encountered
|
||||
summary.append("warnings=" + Integer.toString(warnCount) + "\n");
|
||||
summary.append("exceptions=" + Integer.toString(excCount) + "\n");
|
||||
|
||||
// set the general summary config up in the aggregator file
|
||||
for (int i = 0; i < generalSummary.size(); i++)
|
||||
@@ -725,7 +732,7 @@ public class LogAnalyser
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
return;
|
||||
return summary.toString();
|
||||
}
|
||||
|
||||
|
||||
|
52
dspace-api/src/main/java/org/dspace/health/Check.java
Normal file
52
dspace-api/src/main/java/org/dspace/health/Check.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Abstract check interface.
|
||||
*/
|
||||
|
||||
public abstract class Check {
|
||||
|
||||
protected static Logger log = Logger.getLogger(Check.class);
|
||||
long took_ = -1L;
|
||||
String report_ = null;
|
||||
private String errors_ = "";
|
||||
|
||||
// this method should be overridden
|
||||
protected abstract String run( ReportInfo ri );
|
||||
|
||||
public void report( ReportInfo ri ) {
|
||||
took_ = System.currentTimeMillis();
|
||||
try {
|
||||
String run_report = run(ri);
|
||||
report_ = errors_ + run_report;
|
||||
}finally {
|
||||
took_ = System.currentTimeMillis() - took_;
|
||||
}
|
||||
}
|
||||
|
||||
protected void error( Throwable e ) {
|
||||
error(e, null);
|
||||
}
|
||||
protected void error( Throwable e, String msg ) {
|
||||
errors_ += "====\nException occurred!\n";
|
||||
if ( null != e ) {
|
||||
errors_ += e.toString() + "\n";
|
||||
log.error("Exception during healthcheck:", e);
|
||||
}
|
||||
if ( null != msg ) {
|
||||
errors_ += "Reason: " + msg + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
import org.dspace.checker.*;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class ChecksumCheck extends Check {
|
||||
|
||||
@Override
|
||||
public String run( ReportInfo ri ) {
|
||||
String ret = "No md5 checks made!";
|
||||
CheckerCommand checker = new CheckerCommand();
|
||||
Date process_start = Calendar.getInstance().getTime();
|
||||
checker.setProcessStartDate(process_start);
|
||||
checker.setDispatcher(
|
||||
// new LimitedCountDispatcher(new SimpleDispatcher(new
|
||||
// BitstreamInfoDAO(), null, false), 1)
|
||||
// loop through all files
|
||||
new SimpleDispatcher(new BitstreamInfoDAO(), process_start, false));
|
||||
|
||||
md5_collector collector = new md5_collector();
|
||||
checker.setCollector(collector);
|
||||
checker.setReportVerbose(true);
|
||||
Context context = null;
|
||||
try {
|
||||
context = new Context();
|
||||
checker.process(context);
|
||||
} catch (SQLException e) {
|
||||
error(e);
|
||||
} finally {
|
||||
if (context != null) {
|
||||
context.abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (collector.arr.size() > 0) {
|
||||
ret = String.format("Checksum performed on [%d] items:\n",
|
||||
collector.arr.size());
|
||||
int ok_items = 0;
|
||||
for (BitstreamInfo bi : collector.arr) {
|
||||
if (!ChecksumCheckResults.CHECKSUM_MATCH.equals(bi
|
||||
.getChecksumCheckResult())) {
|
||||
ret += String
|
||||
.format("md5 checksum FAILED (%s): %s id: %s bitstream-id: %s\n was: %s\n is: %s\n",
|
||||
bi.getChecksumCheckResult(), bi.getName(),
|
||||
bi.getInternalId(), bi.getBitstreamId(),
|
||||
bi.getStoredChecksum(),
|
||||
bi.getCalculatedChecksum());
|
||||
} else {
|
||||
ok_items++;
|
||||
}
|
||||
}
|
||||
|
||||
ret += String.format("checksum OK for [%d] items\n", ok_items);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
class md5_collector implements ChecksumResultsCollector {
|
||||
public List<BitstreamInfo> arr = new ArrayList<>();
|
||||
|
||||
public void collect(BitstreamInfo info) {
|
||||
arr.add(info);
|
||||
}
|
||||
}
|
267
dspace-api/src/main/java/org/dspace/health/Core.java
Normal file
267
dspace-api/src/main/java/org/dspace/health/Core.java
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.ItemIterator;
|
||||
import org.dspace.content.Metadatum;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.storage.rdbms.DatabaseManager;
|
||||
import org.dspace.storage.rdbms.TableRow;
|
||||
import org.dspace.storage.rdbms.TableRowIterator;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Core {
|
||||
|
||||
// get info
|
||||
//
|
||||
public static String getCollectionSizesInfo() throws SQLException {
|
||||
String ret = "";
|
||||
List<TableRow> rows = sql(
|
||||
"SELECT "
|
||||
+ "(SELECT text_value FROM metadatavalue "
|
||||
+ "WHERE metadata_field_id=64 AND resource_type_id=3 AND resource_id=col.collection_id) AS name, "
|
||||
+ "SUM(bit.size_bytes) AS sum "
|
||||
+ "FROM collection2item col, item2bundle item, bundle2bitstream bun, bitstream bit "
|
||||
+ "WHERE col.item_id=item.item_id AND item.bundle_id=bun.bundle_id AND bun.bitstream_id=bit.bitstream_id "
|
||||
+ "GROUP BY col.collection_id;");
|
||||
long total_size = 0;
|
||||
for (TableRow row : rows) {
|
||||
double size = row.getLongColumn("sum") / (1024. * 1024.);
|
||||
total_size += size;
|
||||
ret += String.format(
|
||||
"\t%s: %s\n", row.getStringColumn("name"), FileUtils.byteCountToDisplaySize((long)size));
|
||||
}
|
||||
ret += String.format(
|
||||
"Total size: %s\n", FileUtils.byteCountToDisplaySize(total_size));
|
||||
|
||||
ret += String.format(
|
||||
"Resource without policy: %d\n", getBitstreamsWithoutPolicyCount());
|
||||
|
||||
ret += String.format(
|
||||
"Deleted bitstreams: %d\n", getBitstreamsDeletedCount());
|
||||
|
||||
rows = getBitstreamOrphansRows();
|
||||
String list_str = "";
|
||||
for (TableRow row : rows) {
|
||||
list_str += String.format("%d, ", row.getIntColumn("bitstream_id"));
|
||||
}
|
||||
ret += String.format(
|
||||
"Orphan bitstreams: %d [%s]\n", rows.size(), list_str);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static String getObjectSizesInfo() throws SQLException {
|
||||
String ret = "";
|
||||
Context c = new Context();
|
||||
|
||||
for (String tb : new String[] { "bitstream", "bundle", "collection",
|
||||
"community", "dcvalue", "eperson", "item", "handle",
|
||||
"epersongroup", "workflowitem", "workspaceitem", }) {
|
||||
TableRowIterator irows = DatabaseManager.query(c,
|
||||
"SELECT COUNT(*) from " + tb);
|
||||
List<TableRow> rows = irows.toList();
|
||||
ret += String.format("Count %s: %s\n", tb,
|
||||
String.valueOf(rows.get(0).getLongColumn("count")));
|
||||
}
|
||||
|
||||
c.complete();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// get objects
|
||||
//
|
||||
|
||||
public static List<TableRow> getWorkspaceItemsRows() throws SQLException {
|
||||
return sql("SELECT stage_reached, count(1) AS cnt FROM workspaceitem GROUP BY stage_reached ORDER BY stage_reached;");
|
||||
}
|
||||
|
||||
public static List<TableRow> getBitstreamOrphansRows() throws SQLException {
|
||||
return sql("SELECT bitstream_id FROM bitstream WHERE deleted<>true AND bitstream_id "
|
||||
+ "NOT IN ("
|
||||
+ "SELECT bitstream_id FROM bundle2bitstream "
|
||||
+ "UNION SELECT logo_bitstream_id FROM community WHERE logo_bitstream_id IS NOT NULL "
|
||||
+ "UNION SELECT primary_bitstream_id FROM bundle WHERE primary_bitstream_id IS NOT NULL ORDER BY bitstream_id "
|
||||
+ ")");
|
||||
|
||||
}
|
||||
|
||||
public static List<TableRow> getSubscribersRows() throws SQLException {
|
||||
return sql("SELECT DISTINCT ON (eperson_id) eperson_id FROM subscription");
|
||||
}
|
||||
|
||||
public static List<TableRow> getSubscribedCollectionsRows() throws SQLException {
|
||||
return sql("SELECT DISTINCT ON (collection_id) collection_id FROM subscription");
|
||||
}
|
||||
|
||||
public static List<TableRow> getHandlesInvalidRows() throws SQLException {
|
||||
List<TableRow> rows = sql("SELECT * FROM handle "
|
||||
+ " WHERE NOT ("
|
||||
+ " (handle IS NOT NULL AND resource_type_id IS NOT NULL AND resource_id IS NOT NULL)"
|
||||
+ " OR " + " (handle IS NOT NULL AND url IS NOT NULL)"
|
||||
+ " ) ");
|
||||
return rows;
|
||||
}
|
||||
|
||||
// get sizes
|
||||
//
|
||||
|
||||
public static int getItemsTotalCount() throws SQLException {
|
||||
int total = 0;
|
||||
for (java.util.Map.Entry<String, Integer> name_count : getCommunities()) {
|
||||
total += name_count.getValue();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public static int getWorkflowItemsCount() throws SQLException {
|
||||
return sql("SELECT * FROM workflowitem;").size();
|
||||
}
|
||||
|
||||
public static int getNotArchivedItemsCount() throws SQLException {
|
||||
return sql(
|
||||
"SELECT * FROM item WHERE in_archive=false AND withdrawn=false").size();
|
||||
}
|
||||
|
||||
public static int getWithdrawnItemsCount() throws SQLException {
|
||||
return sql("SELECT * FROM item WHERE withdrawn=true").size();
|
||||
}
|
||||
|
||||
public static int getBitstreamsWithoutPolicyCount() throws SQLException {
|
||||
return sql(
|
||||
"SELECT bitstream_id FROM bitstream WHERE deleted<>true AND bitstream_id NOT IN "
|
||||
+ "(SELECT resource_id FROM resourcepolicy WHERE resource_type_id=0)")
|
||||
.size();
|
||||
}
|
||||
|
||||
public static int getBitstreamsDeletedCount() throws SQLException {
|
||||
return sql("SELECT * FROM bitstream WHERE deleted=true").size();
|
||||
}
|
||||
|
||||
public static long getHandlesTotalCount() throws SQLException {
|
||||
List<TableRow> rows = sql("SELECT count(1) AS cnt FROM handle");
|
||||
return rows.get(0).getLongColumn("cnt");
|
||||
}
|
||||
|
||||
|
||||
// get more complex information
|
||||
//
|
||||
|
||||
public static List<Map.Entry<String, Integer>> getCommunities()
|
||||
throws SQLException {
|
||||
|
||||
List<Map.Entry<String, Integer>> cl = new java.util.ArrayList<>();
|
||||
Context context = new Context();
|
||||
Community[] top_communities = Community.findAllTop(context);
|
||||
for (Community c : top_communities) {
|
||||
cl.add(
|
||||
new java.util.AbstractMap.SimpleEntry<>(c.getName(), c.countItems())
|
||||
);
|
||||
}
|
||||
context.complete();
|
||||
return cl;
|
||||
}
|
||||
|
||||
public static List<String> getEmptyGroups() throws SQLException {
|
||||
List<String> ret = new ArrayList<>();
|
||||
Context c = new Context();
|
||||
TableRowIterator irows = DatabaseManager
|
||||
.query(c,
|
||||
"SELECT eperson_group_id, "
|
||||
+ "(SELECT text_value FROM metadatavalue "
|
||||
+ "WHERE metadata_field_id=64 AND resource_type_id=6 AND resource_id=eperson_group_id) AS name "
|
||||
+ "FROM epersongroup "
|
||||
+ "WHERE eperson_group_id NOT IN (SELECT eperson_group_id FROM epersongroup2eperson)");
|
||||
for (TableRow row : irows.toList()) {
|
||||
ret.add( row.getStringColumn("name") );
|
||||
}
|
||||
c.complete();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static List<Integer> getSubscribers() throws SQLException {
|
||||
List<Integer> ret = new ArrayList<>();
|
||||
for (TableRow row : getSubscribersRows()) {
|
||||
ret.add(row.getIntColumn("eperson_id"));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static List<Integer> getSubscribedCollections() throws SQLException {
|
||||
List<Integer> ret = new ArrayList<>();
|
||||
for (TableRow row : getSubscribedCollectionsRows()) {
|
||||
ret.add(row.getIntColumn("collection_id"));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Map<String, String> getItemRightsInfo() {
|
||||
Map<String, String> ret = new HashMap<>();
|
||||
Map<String, Integer> info = new HashMap<>();
|
||||
try {
|
||||
Context context = new Context();
|
||||
ItemIterator it = Item.findAll(context);
|
||||
while (it.hasNext()) {
|
||||
Item i = it.next();
|
||||
Metadatum[] labels = i.getMetadata("dc", "rights", "label",
|
||||
Item.ANY);
|
||||
String pub_dc_value = "";
|
||||
|
||||
if (labels.length > 0) {
|
||||
for (Metadatum dc : labels) {
|
||||
if (pub_dc_value.length() == 0) {
|
||||
pub_dc_value = dc.value;
|
||||
} else {
|
||||
pub_dc_value = pub_dc_value + " " + dc.value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pub_dc_value = "no licence";
|
||||
}
|
||||
|
||||
if (!info.containsKey(pub_dc_value)) {
|
||||
info.put(pub_dc_value, 0);
|
||||
}
|
||||
info.put(pub_dc_value, info.get(pub_dc_value) + 1);
|
||||
}
|
||||
context.complete();
|
||||
|
||||
for (Map.Entry<String, Integer> e : info.entrySet()) {
|
||||
ret.put(e.getKey(), String.valueOf(e.getValue()));
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
static List<TableRow> sql(String sql) throws SQLException {
|
||||
Context c = new Context();
|
||||
List<TableRow> ret = DatabaseManager.query(c, sql).toList();
|
||||
c.complete();
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
64
dspace-api/src/main/java/org/dspace/health/EmbargoCheck.java
Normal file
64
dspace-api/src/main/java/org/dspace/health/EmbargoCheck.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
|
||||
import org.dspace.content.DCDate;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.embargo.factory.EmbargoServiceFactory;
|
||||
import org.dspace.embargo.service.EmbargoService;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class EmbargoCheck extends Check {
|
||||
|
||||
private static final EmbargoService embargoService = EmbargoServiceFactory.getInstance().getEmbargoService();
|
||||
|
||||
@Override
|
||||
public String run( ReportInfo ri ) {
|
||||
String ret = "";
|
||||
Context context = null;
|
||||
try {
|
||||
context = new Context();
|
||||
Iterator<Item> item_iter = null;
|
||||
try {
|
||||
item_iter = embargoService.findItemsByLiftMetadata(context);
|
||||
} catch (IllegalArgumentException e) {
|
||||
error(e, "No embargoed items found");
|
||||
} catch (Exception e) {
|
||||
error(e);
|
||||
}
|
||||
|
||||
while (item_iter != null && item_iter.hasNext()) {
|
||||
Item item = item_iter.next();
|
||||
String handle = item.getHandle();
|
||||
DCDate date = null;
|
||||
try {
|
||||
date = embargoService.getEmbargoTermsAsDate(context, item);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
ret += String.format("%s embargoed till [%s]\n", handle,
|
||||
date != null ? date.toString() : "null");
|
||||
}
|
||||
context.complete();
|
||||
} catch (SQLException e) {
|
||||
try {
|
||||
if ( null != context ) {
|
||||
context.abort();
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
69
dspace-api/src/main/java/org/dspace/health/InfoCheck.java
Normal file
69
dspace-api/src/main/java/org/dspace/health/InfoCheck.java
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.dspace.core.ConfigurationManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
public class InfoCheck extends Check {
|
||||
|
||||
@Override
|
||||
public String run( ReportInfo ri ) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Generated: ").append(
|
||||
new Date().toString()
|
||||
).append("\n");
|
||||
|
||||
sb.append("From - Till: ").append(
|
||||
new SimpleDateFormat("MM/dd/yyyy").format(ri.from().getTime())
|
||||
).append(" - ").append(
|
||||
new SimpleDateFormat("MM/dd/yyyy").format(ri.till().getTime())
|
||||
).append("\n");
|
||||
|
||||
sb.append("Url: ").append(
|
||||
ConfigurationManager.getProperty("dspace.url")
|
||||
).append("\n");
|
||||
sb.append("\n");
|
||||
|
||||
for (String[] ss : new String[][] {
|
||||
new String[] {
|
||||
ConfigurationManager.getProperty("assetstore.dir"),
|
||||
"Assetstore size: ", },
|
||||
new String[] {
|
||||
ConfigurationManager.getProperty("search.dir"),
|
||||
"Search dir size: ", },
|
||||
new String[] {
|
||||
ConfigurationManager.getProperty("log.dir"),
|
||||
"Log dir size: ", }, })
|
||||
{
|
||||
try {
|
||||
File dir = new File(ss[0]);
|
||||
if (dir.exists()) {
|
||||
long dir_size = FileUtils.sizeOfDirectory(dir);
|
||||
sb.append(String.format("%s: %s\n", ss[1],
|
||||
FileUtils.byteCountToDisplaySize(dir_size))
|
||||
);
|
||||
} else {
|
||||
sb.append(String.format("Directory [%s] does not exist!\n", ss[0]));
|
||||
}
|
||||
}catch(Exception e) {
|
||||
error(e, "directory - " + ss[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
71
dspace-api/src/main/java/org/dspace/health/ItemCheck.java
Normal file
71
dspace-api/src/main/java/org/dspace/health/ItemCheck.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
|
||||
import org.dspace.storage.rdbms.TableRow;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
public class ItemCheck extends Check {
|
||||
|
||||
@Override
|
||||
public String run( ReportInfo ri ) {
|
||||
String ret = "";
|
||||
int tot_cnt = 0;
|
||||
try {
|
||||
for (Map.Entry<String, Integer> name_count : Core.getCommunities()) {
|
||||
ret += String.format("Collection [%s]: %d\n",
|
||||
name_count.getKey(), name_count.getValue());
|
||||
tot_cnt += name_count.getValue();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
error(e);
|
||||
}
|
||||
|
||||
try {
|
||||
ret += "\nCollection sizes:\n";
|
||||
ret += Core.getCollectionSizesInfo();
|
||||
} catch (SQLException e) {
|
||||
error(e);
|
||||
}
|
||||
|
||||
ret += String.format(
|
||||
"\nPublished items (archived, not withdrawn): %d\n", tot_cnt);
|
||||
try {
|
||||
ret += String.format(
|
||||
"Withdrawn items: %d\n", Core.getWithdrawnItemsCount());
|
||||
ret += String.format(
|
||||
"Not published items (in workspace or workflow mode): %d\n",
|
||||
Core.getNotArchivedItemsCount());
|
||||
|
||||
for (TableRow row : Core.getWorkspaceItemsRows()) {
|
||||
ret += String.format("\tIn Stage %s: %s\n",
|
||||
row.getIntColumn("stage_reached"),
|
||||
row.getLongColumn("cnt"));
|
||||
}
|
||||
|
||||
ret += String.format(
|
||||
"\tWaiting for approval (workflow items): %d\n",
|
||||
Core.getWorkflowItemsCount());
|
||||
|
||||
} catch (SQLException e) {
|
||||
error(e);
|
||||
}
|
||||
|
||||
try {
|
||||
ret += Core.getObjectSizesInfo();
|
||||
} catch (SQLException e) {
|
||||
error(e);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
|
||||
import org.dspace.app.statistics.LogAnalyser;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LogAnalyserCheck extends Check {
|
||||
|
||||
final static private String[][] interesting_fields = new String[][] {
|
||||
new String[] { "exceptions", "Exceptions" },
|
||||
new String[] { "warnings", "Warnings" },
|
||||
new String[] { "action.browse", "Archive browsed" },
|
||||
new String[] { "action.search", "Archive searched" },
|
||||
new String[] { "action.login", "Logged in" },
|
||||
new String[] { "action.oai_request", "OAI requests" },
|
||||
};
|
||||
|
||||
@Override
|
||||
public String run( ReportInfo ri ) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
Map<String, String> info_map = new HashMap<>();
|
||||
for (String[] info : interesting_fields) {
|
||||
info_map.put(info[0], "unknown");
|
||||
}
|
||||
|
||||
try {
|
||||
Context c = new Context();
|
||||
// parse logs
|
||||
String report = LogAnalyser.processLogs(
|
||||
c, null, null, null, null, ri.from(), ri.till(), false);
|
||||
|
||||
// we have to deal with string report...
|
||||
for (String line : report.split("\\r?\\n")) {
|
||||
String[] parts = line.split("=");
|
||||
if (parts.length == 2) {
|
||||
info_map.put(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// create report
|
||||
for (String[] info : interesting_fields ) {
|
||||
sb.append( String.format("%-17s: %s\n", info[1], info_map.get(info[0])) );
|
||||
}
|
||||
sb.append( String.format("Items added since [%s] (db): %s\n",
|
||||
new SimpleDateFormat("MM/dd/yyyy").format(ri.from().getTime()),
|
||||
LogAnalyser.getNumItems(c)));
|
||||
|
||||
c.complete();
|
||||
|
||||
} catch (Exception e) {
|
||||
error(e);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
211
dspace-api/src/main/java/org/dspace/health/Report.java
Normal file
211
dspace-api/src/main/java/org/dspace/health/Report.java
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.dspace.core.ConfigurationManager;
|
||||
import org.dspace.core.Email;
|
||||
import org.dspace.core.PluginManager;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class Report {
|
||||
|
||||
private static Logger log = Logger.getLogger(Report.class);
|
||||
public static final String EMAIL_PATH = "config/emails/healthcheck";
|
||||
// store the individual check reports
|
||||
private StringBuilder summary_;
|
||||
|
||||
// ctor
|
||||
//
|
||||
public Report() {
|
||||
summary_ = new StringBuilder();
|
||||
}
|
||||
|
||||
// run checks
|
||||
//
|
||||
public void run(List<Integer> to_perform, ReportInfo ri) {
|
||||
|
||||
int pos = -1;
|
||||
for (Entry<String, Check> check_entry : checks().entrySet()) {
|
||||
++pos;
|
||||
if ( null != to_perform && !to_perform.contains(pos) ) {
|
||||
continue;
|
||||
}
|
||||
String check_name = check_entry.getKey();
|
||||
Check check = check_entry.getValue();
|
||||
|
||||
log.info(String.format("#%d. Processing [%s] at [%s]",
|
||||
pos, check_name, new SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss.SSS").format(new Date())));
|
||||
|
||||
try {
|
||||
// do the stuff
|
||||
check.report(ri);
|
||||
store(check_name, check.took_, check.report_);
|
||||
|
||||
}catch( Exception e ) {
|
||||
store(
|
||||
check_name,
|
||||
-1,
|
||||
"Exception occurred when processing report - " + ExceptionUtils.getStackTrace(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create check list
|
||||
public static LinkedHashMap<String, Check> checks() {
|
||||
LinkedHashMap<String, Check> checks = new LinkedHashMap<>();
|
||||
String check_names[] = ConfigurationManager.getProperty("healthcheck", "checks").split(",");
|
||||
for ( String check_name : check_names ) {
|
||||
Check check = (Check) PluginManager.getNamedPlugin(
|
||||
"healthcheck", Check.class, check_name);
|
||||
if ( null != check ) {
|
||||
checks.put(check_name, check);
|
||||
}else {
|
||||
log.warn( String.format(
|
||||
"Could not find implementation for [%s]", check_name) );
|
||||
}
|
||||
}
|
||||
return checks;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return summary_.toString();
|
||||
}
|
||||
|
||||
//
|
||||
private void store(String name, long took, String report) {
|
||||
name += String.format(" [took: %ds] [# lines: %d]",
|
||||
took / 1000,
|
||||
new StringTokenizer(report, "\r\n").countTokens()
|
||||
);
|
||||
|
||||
String one_summary = String.format(
|
||||
"\n#### %s\n%s\n\n###############################\n",
|
||||
name,
|
||||
report.replaceAll("\\s+$", "")
|
||||
);
|
||||
summary_.append(one_summary);
|
||||
|
||||
// output it
|
||||
System.out.println(one_summary);
|
||||
|
||||
}
|
||||
|
||||
// main
|
||||
//
|
||||
|
||||
public static void main(String[] args) {
|
||||
log.info("Starting healthcheck report...");
|
||||
|
||||
final String option_help = "h";
|
||||
final String option_email = "e";
|
||||
final String option_check = "c";
|
||||
final String option_last_n = "f";
|
||||
final String option_verbose = "v";
|
||||
|
||||
// command line options
|
||||
Options options = new Options();
|
||||
options.addOption(option_help, "help", false,
|
||||
"Show available checks and their index.");
|
||||
options.addOption(option_email, "email", true,
|
||||
"Send report to this email address.");
|
||||
options.addOption(option_check, "check", true,
|
||||
"Perform only specific check (use index starting from 0).");
|
||||
options.addOption(option_last_n, "for", true,
|
||||
"For last N days.");
|
||||
options.addOption(option_verbose, "verbose", false,
|
||||
"Verbose report.");
|
||||
|
||||
CommandLine cmdline = null;
|
||||
try {
|
||||
cmdline = new PosixParser().parse(options, args);
|
||||
} catch (ParseException e) {
|
||||
log.fatal("Invalid command line " + e.toString(), e);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if ( cmdline.hasOption(option_help) ) {
|
||||
String checks_summary = "";
|
||||
int pos = 0;
|
||||
for (String check_name: checks().keySet()) {
|
||||
checks_summary += String.format( "%d. %s\n", pos++, check_name );
|
||||
}
|
||||
System.out.println( "Available checks:\n" + checks_summary );
|
||||
return;
|
||||
}
|
||||
|
||||
// what to perform
|
||||
List<Integer> to_perform = null;
|
||||
if ( null != cmdline.getOptionValues(option_check)) {
|
||||
to_perform = new ArrayList<>();
|
||||
for (String s : cmdline.getOptionValues('c')) {
|
||||
to_perform.add(Integer.valueOf(s));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// last n days
|
||||
int for_last_n_days = ConfigurationManager.getIntProperty(
|
||||
"healthcheck", "last_n_days");
|
||||
if ( cmdline.hasOption(option_last_n) ) {
|
||||
for_last_n_days = Integer.getInteger(
|
||||
cmdline.getOptionValue(option_last_n));
|
||||
}
|
||||
ReportInfo ri = new ReportInfo( for_last_n_days );
|
||||
if ( cmdline.hasOption(option_verbose) ) {
|
||||
ri.verbose( true );
|
||||
}
|
||||
|
||||
// run report
|
||||
Report r = new Report();
|
||||
r.run(to_perform, ri);
|
||||
log.info("reports generated...");
|
||||
|
||||
// send/output the report
|
||||
if (cmdline.hasOption(option_email)) {
|
||||
String to = cmdline.getOptionValue(option_email);
|
||||
if ( !to.contains("@") ) {
|
||||
to = ConfigurationManager.getProperty(to);
|
||||
}
|
||||
try {
|
||||
String dspace_dir = ConfigurationManager.getProperty("dspace.dir");
|
||||
String email_path = dspace_dir.endsWith("/") ? dspace_dir
|
||||
: dspace_dir + "/";
|
||||
email_path += Report.EMAIL_PATH;
|
||||
log.info(String.format(
|
||||
"Looking for email template at [%s]", email_path));
|
||||
Email email = Email.getEmail(email_path);
|
||||
email.addRecipient(to);
|
||||
email.addArgument(r.toString());
|
||||
email.send();
|
||||
} catch (Exception e) {
|
||||
log.fatal("Error sending email:", e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.fatal(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
54
dspace-api/src/main/java/org/dspace/health/ReportInfo.java
Normal file
54
dspace-api/src/main/java/org/dspace/health/ReportInfo.java
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import static java.util.Calendar.DAY_OF_MONTH;
|
||||
import static java.util.Calendar.MONTH;
|
||||
import static java.util.Calendar.YEAR;
|
||||
|
||||
/**
|
||||
* Information about a report run accessible by each check.
|
||||
*/
|
||||
public class ReportInfo {
|
||||
|
||||
private boolean verbose_;
|
||||
private GregorianCalendar from_ = null;
|
||||
private GregorianCalendar till_ = null;
|
||||
|
||||
public ReportInfo(int for_last_n_days) {
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
till_ = new GregorianCalendar(
|
||||
cal.get(YEAR), cal.get(MONTH), cal.get(DAY_OF_MONTH)
|
||||
);
|
||||
// get info from the last n days
|
||||
from_ = (GregorianCalendar)till_.clone();
|
||||
from_.add(DAY_OF_MONTH, -for_last_n_days);
|
||||
// filter output
|
||||
verbose_ = false;
|
||||
}
|
||||
|
||||
public void verbose( boolean verbose ) {
|
||||
verbose_ = verbose;
|
||||
}
|
||||
public boolean verbose() {
|
||||
return verbose_;
|
||||
}
|
||||
|
||||
public Date from() {
|
||||
return from_.getTime();
|
||||
}
|
||||
|
||||
public Date till() {
|
||||
return till_.getTime();
|
||||
}
|
||||
}
|
97
dspace-api/src/main/java/org/dspace/health/UserCheck.java
Normal file
97
dspace-api/src/main/java/org/dspace/health/UserCheck.java
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*
|
||||
* by lindat-dev team
|
||||
*/
|
||||
package org.dspace.health;
|
||||
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UserCheck extends Check {
|
||||
|
||||
@Override
|
||||
public String run( ReportInfo ri ) {
|
||||
String ret = "";
|
||||
Map<String, Integer> info = new HashMap<String, Integer>();
|
||||
try {
|
||||
Context context = new Context();
|
||||
EPerson[] epersons = EPerson.findAll(context, EPerson.LASTNAME);
|
||||
info.put("Count", epersons.length);
|
||||
info.put("Can log in (password)", 0);
|
||||
info.put("Have email", 0);
|
||||
info.put("Have 1st name", 0);
|
||||
info.put("Have 2nd name", 0);
|
||||
info.put("Have lang", 0);
|
||||
info.put("Have netid", 0);
|
||||
info.put("Self registered", 0);
|
||||
|
||||
for (EPerson e : epersons) {
|
||||
if (e.getEmail() != null && e.getEmail().length() > 0)
|
||||
info.put("Have email", info.get("Have email") + 1);
|
||||
if (e.canLogIn())
|
||||
info.put("Can log in (password)",
|
||||
info.get("Can log in (password)") + 1);
|
||||
if (e.getFirstName() != null && e.getFirstName().length() > 0)
|
||||
info.put("Have 1st name", info.get("Have 1st name") + 1);
|
||||
if (e.getLastName() != null && e.getLastName().length() > 0)
|
||||
info.put("Have 2nd name", info.get("Have 2nd name") + 1);
|
||||
if (e.getLanguage() != null && e.getLanguage().length() > 0)
|
||||
info.put("Have lang", info.get("Have lang") + 1);
|
||||
if (e.getNetid() != null && e.getNetid().length() > 0)
|
||||
info.put("Have netid", info.get("Have netid") + 1);
|
||||
if (e.getNetid() != null && e.getNetid().length() > 0)
|
||||
info.put("Self registered", info.get("Self registered") + 1);
|
||||
}
|
||||
context.complete();
|
||||
|
||||
} catch (SQLException e) {
|
||||
error(e);
|
||||
}
|
||||
|
||||
ret += String.format(
|
||||
"Users: %d\n", info.get("Count"));
|
||||
ret += String.format(
|
||||
"Have email: %d\n", info.get("Have email"));
|
||||
for (Map.Entry<String, Integer> e : info.entrySet()) {
|
||||
if (!e.getKey().equals("Count") && !e.getKey().equals("Have email")) {
|
||||
ret += String.format("%s: %s\n", e.getKey(),
|
||||
String.valueOf(e.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// empty group
|
||||
List<String> egs = Core.getEmptyGroups();
|
||||
ret += String.format(
|
||||
" Empty groups: #%d\n %s\n",
|
||||
egs.size(), StringUtils.join(egs, ",\n "));
|
||||
|
||||
List<Integer> subs = Core.getSubscribers();
|
||||
ret += String.format(
|
||||
"Subscribers: #%d [%s]\n",
|
||||
subs.size(), StringUtils.join(subs, ", "));
|
||||
|
||||
subs = Core.getSubscribedCollections();
|
||||
ret += String.format(
|
||||
"Subscribed cols.: #%d [%s]\n",
|
||||
subs.size(), StringUtils.join(subs, ", "));
|
||||
|
||||
} catch (SQLException e) {
|
||||
error(e);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
7
dspace/config/emails/healthcheck
Normal file
7
dspace/config/emails/healthcheck
Normal file
@@ -0,0 +1,7 @@
|
||||
Subject: ${dspace.name}: Repository healthcheck
|
||||
{0}
|
||||
|
||||
|
||||
_____________________________________
|
||||
${dspace.name},
|
||||
WWW: ${dspace.url}
|
@@ -7,6 +7,13 @@
|
||||
<class>org.dspace.storage.bitstore.BitStoreMigrate</class>
|
||||
</step>
|
||||
</command>
|
||||
<command>
|
||||
<name>healthcheck</name>
|
||||
<description>Create health check report</description>
|
||||
<step>
|
||||
<class>org.dspace.health.Report</class>
|
||||
</step>
|
||||
</command>
|
||||
<command>
|
||||
<name>checker</name>
|
||||
<description>Run the checksum checker</description>
|
||||
|
20
dspace/config/modules/healthcheck.cfg
Normal file
20
dspace/config/modules/healthcheck.cfg
Normal file
@@ -0,0 +1,20 @@
|
||||
### Healthcheck module config
|
||||
|
||||
# names must match plugin.named below
|
||||
checks = General Information,\
|
||||
Checksum,\
|
||||
Embargo items,\
|
||||
Item summary,\
|
||||
User summary,\
|
||||
Log Analyser Check
|
||||
|
||||
plugin.named.org.dspace.health.Check = \
|
||||
org.dspace.health.InfoCheck = General Information,\
|
||||
org.dspace.health.ChecksumCheck = Checksum,\
|
||||
org.dspace.health.EmbargoCheck = Embargo items,\
|
||||
org.dspace.health.ItemCheck = Item summary,\
|
||||
org.dspace.health.UserCheck = User summary,\
|
||||
org.dspace.health.LogAnalyserCheck = Log Analyser Check
|
||||
|
||||
# report from the last N days (where dates are applicable)
|
||||
last_n_days = 7
|
Reference in New Issue
Block a user