Skip to content

Commit

Permalink
Add draft of activity progress "API"
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly committed Jun 11, 2021
1 parent 1ca7faa commit 785186e
Show file tree
Hide file tree
Showing 17 changed files with 575 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
/*
* Copyright (C) 2010-2021 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.schema.util.task;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

import org.jetbrains.annotations.NotNull;

/**
* Summarized representation of a progress of an activity and its sub-activities.
*
* Examples:
*
* - 23% in 1/3
* - 23% in 2/2 in 2/3
*
* TODO optimize task reading: avoid doing that for completed subtasks
*
* TODO i8n
*/
public class ActivityProgressInformation implements DebugDumpable, Serializable {

private static final Trace LOGGER = TraceManager.getTrace(ActivityProgressInformation.class);

/**
* Activity identifier.
*/
private final String activityIdentifier;

/**
* Is this activity complete?
*/
private final RealizationState realizationState;

/**
* Progress in the language of buckets.
* (Filled-in also for single-bucket activities, although it provides no usable information for them.)
*
* Ignored if there are children.
*/
private final BucketsProgressInformation bucketsProgress;

/**
* Progress in the language of items (total).
*
* Ignored if there are children.
*/
private final ItemsProgressInformation itemsProgress;

@NotNull private final List<ActivityProgressInformation> children = new ArrayList<>();

private ActivityProgressInformation(String activityIdentifier, RealizationState realizationState,
BucketsProgressInformation bucketsProgress, ItemsProgressInformation itemsProgress) {
this.activityIdentifier = activityIdentifier;
this.realizationState = realizationState;
this.bucketsProgress = bucketsProgress;
this.itemsProgress = itemsProgress;
}

private static @NotNull ActivityProgressInformation unknown(String activityIdentifier) {
return new ActivityProgressInformation(activityIdentifier, RealizationState.UNKNOWN, null, null);
}

/**
* Prepares the information from a root task. The task may or may not have its children resolved.
*/
public static ActivityProgressInformation fromTask(@NotNull TaskType task, @NotNull TaskResolver resolver) {
TaskActivityStateType globalState = task.getActivityState();
ActivityStateType rootActivityState = globalState.getActivity();
return fromDelegatableActivityState(rootActivityState, task, resolver);
}

private static ActivityProgressInformation fromDelegatableActivityState(@NotNull ActivityStateType state,
@NotNull TaskType task, @NotNull TaskResolver resolver) {
if (state.getRealizationState() == ActivityRealizationStateType.IN_PROGRESS_DELEGATED) {
return fromDelegatedActivityState(state.getIdentifier(), getDelegatedTaskRef(state), task, resolver);
} else {
return fromNotDelegatedActivityState(state, task, resolver);
}
}

private static ObjectReferenceType getDelegatedTaskRef(ActivityStateType state) {
AbstractActivityWorkStateType workState = state.getWorkState();
return workState instanceof DelegationWorkStateType ? ((DelegationWorkStateType) workState).getTaskRef() : null;
}

private static ActivityProgressInformation fromDelegatedActivityState(String activityIdentifier,
ObjectReferenceType delegateTaskRef, @NotNull TaskType task, @NotNull TaskResolver resolver) {
TaskType delegateTask = getSubtask(delegateTaskRef, task, resolver);
if (delegateTask != null) {
return fromTask(delegateTask, resolver);
} else {
return unknown(activityIdentifier);
}
}

private static TaskType getSubtask(ObjectReferenceType subtaskRef, TaskType task, TaskResolver resolver) {
String subTaskOid = subtaskRef != null ? subtaskRef.getOid() : null;
if (subTaskOid == null) {
return null;
}
TaskType inTask = TaskTreeUtil.findChildIfResolved(task, subTaskOid);
if (inTask != null) {
return inTask;
}
try {
return resolver.resolve(subTaskOid);
} catch (ObjectNotFoundException | SchemaException e) {
LoggingUtils.logException(LOGGER, "Couldn't retrieve subtask {} of {}", e, subTaskOid, task);
return null;
}
}

private static ActivityProgressInformation fromNotDelegatedActivityState(@NotNull ActivityStateType state,
@NotNull TaskType task, @NotNull TaskResolver resolver) {
String identifier = state.getIdentifier();
RealizationState realizationState = getRealizationState(state);
BucketsProgressInformation bucketsProgress = BucketsProgressInformation.fromActivityState(state);
ItemsProgressInformation itemsProgress = null; // TODO

ActivityProgressInformation info
= new ActivityProgressInformation(identifier, realizationState, bucketsProgress, itemsProgress);
for (ActivityStateType childState : state.getActivity()) {
info.children.add(fromDelegatableActivityState(childState, task, resolver));
}
return info;
}

private static RealizationState getRealizationState(ActivityStateType state) {
ActivityRealizationStateType rawState = state.getRealizationState();
if (rawState == null) {
return null;
} else if (rawState == ActivityRealizationStateType.COMPLETE) {
return RealizationState.COMPLETE;
} else {
return RealizationState.IN_PROGRESS;
}
}

public String getActivityIdentifier() {
return activityIdentifier;
}

public RealizationState getRealizationState() {
return realizationState;
}

public BucketsProgressInformation getBucketsProgress() {
return bucketsProgress;
}

public ItemsProgressInformation getItemsProgress() {
return itemsProgress;
}

public @NotNull List<ActivityProgressInformation> getChildren() {
return children;
}

@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"identifier=" + activityIdentifier +
", state=" + realizationState +
", bucketsProgress=" + bucketsProgress +
", totalItemsProgress=" + itemsProgress +
", children: " + children.size() +
'}';
}

@Override
public String debugDump(int indent) {
String title = getClass().getSimpleName() + ": " + toHumanReadableString(false);
StringBuilder sb = DebugUtil.createTitleStringBuilder(title, indent);
sb.append("\n");
DebugUtil.debugDumpWithLabelLn(sb, "Human readable string (long)", toHumanReadableString(true), indent + 1);
DebugUtil.debugDumpWithLabelLn(sb, "Identifier", activityIdentifier, indent + 1);
DebugUtil.debugDumpWithLabel(sb, "Realization state", realizationState, indent + 1);
if (bucketsProgress != null) {
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "Buckets progress", bucketsProgress, indent + 1);
}
if (itemsProgress != null) {
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "Total items progress", itemsProgress, indent + 1);
}
if (!children.isEmpty()) {
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "Children", children, indent + 1);
}
return sb.toString();
}

public String toHumanReadableString(boolean longForm) {
if (children.isEmpty()) {
return toHumanReadableStringForLeaf(longForm);
} else {
return toHumanReadableStringForNonLeaf(longForm);
}
}

private String toHumanReadableStringForLeaf(boolean longForm) {
if (isNotStarted()) {
return "Not started";
} else if (shouldUseBucketForProgressReporting()) {
return toHumanReadableStringForBucketed(longForm);
} else if (itemsProgress != null) {
return toHumanReadableStringForNonBucketed(longForm);
} else {
return isComplete() ? "Complete" : "Processing";
}
}

/**
* Related to {@link BucketingUtil#hasBuckets(TaskType)}. But unfortunately we do not have the full information
* here, in particular we don't see the actual buckets. This should be probably fixed in the pre-processing phase
* i.e. when {@link ActivityProgressInformation} is created.
*
* TODO ???
*/
private boolean shouldUseBucketForProgressReporting() {
if (bucketsProgress == null) {
return false;
}
if (bucketsProgress.getExpectedBuckets() != null) {
// - If > 1: There are some buckets expected. Even if it is a small number, we consider the task as bucketed.
// - Otherwise: A single bucket. There is no point in showing performance information in buckets for such tasks.
// We will use items progress instead.
return bucketsProgress.getExpectedBuckets() > 1;
} else {
// We don't know how many buckets to expect. So let's guess according to buckets completed so far.
return bucketsProgress.getCompletedBuckets() > 1;
}
}

private String toHumanReadableStringForBucketed(boolean longForm) {
float percentage = bucketsProgress.getPercentage();
if (Float.isNaN(percentage)) {
if (longForm) {
return bucketsProgress.getCompletedBuckets() + " buckets";
} else {
return bucketsProgress.getCompletedBuckets() + " buckets"; // at least temporarily until we find something better
}
}
if (longForm) {
return String.format("%.1f%% (%d of %d buckets)", percentage * 100,
bucketsProgress.getCompletedBuckets(), bucketsProgress.getExpectedBuckets());
} else {
return String.format("%.1f%%", percentage * 100);
}
}

private String toHumanReadableStringForNonBucketed(boolean longForm) {
float percentage = itemsProgress.getPercentage();
if (Float.isNaN(percentage)) {
return String.valueOf(itemsProgress.getProgress());
}
if (longForm) {
return String.format("%.1f%% (%d of %d)", percentage * 100,
itemsProgress.getProgress(), itemsProgress.getExpectedTotal());
} else {
return String.format("%.1f%%", percentage * 100);
}
}

private String toHumanReadableStringForNonLeaf(boolean longForm) {
if (isComplete()) {
return "Complete";
}

if (children.size() == 1) {
return children.get(0).toHumanReadableString(longForm);
}

List<String> partials = new ArrayList<>();
for (int i = 0; i < children.size(); i++) {
ActivityProgressInformation child = children.get(i);
if (child.isInProgress()) {
partials.add(child.toHumanReadableString(longForm) + " " + getPositionSuffix(i, longForm));
} else if (child.isUnknown()) {
partials.add("? " + getPositionSuffix(i, longForm));
}
}

if (partials.isEmpty()) {
return "?"; // something strange
} else {
return String.join(" & ", partials);
}
}

private String getPositionSuffix(int i, boolean longForm) {
return longForm
? String.format("in %d of %d", i + 1, children.size())
: String.format("in %d/%d", i + 1, children.size());
}

private boolean isInProgress() {
return realizationState == RealizationState.IN_PROGRESS;
}

private boolean isUnknown() {
return realizationState == RealizationState.UNKNOWN;
}

public boolean isComplete() {
return realizationState == RealizationState.COMPLETE;
}

public boolean isNotStarted() {
return realizationState == null;
}

public void checkConsistence() {
if (bucketsProgress != null) {
bucketsProgress.checkConsistence();
}
if (itemsProgress != null) {
itemsProgress.checkConsistence();
}
}

public interface TaskResolver {
TaskType resolve(String oid) throws SchemaException, ObjectNotFoundException;
}

public enum RealizationState {
/**
* The activity is in progress: it was started but not completed yet.
* It may or may not be executing at this moment.
*/
IN_PROGRESS,

/**
* The activity is complete.
*/
COMPLETE,

/**
* The state and progress of the activity is unknown. For example, the task it was delegated to is no longer available.
*/
UNKNOWN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,7 @@ private static List<String> getLocalRootSegments(@NotNull TaskActivityStateType
return workState.getLocalRoot() != null ? workState.getLocalRoot().getIdentifier() : List.of();
}

public static boolean isComplete(@NotNull ActivityStateType state) {
return state.getRealizationState() == ActivityRealizationStateType.COMPLETE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ public static int getCompleteBucketsNumber(TaskActivityStateType state) {
}
}

public static int getCompleteBucketsNumber(ActivityBucketingStateType bucketing) {
if (bucketing == null) {
return 0;
}
Integer max = null;
int notComplete = 0;
for (WorkBucketType bucket : bucketing.getBucket()) {
if (max == null || bucket.getSequentialNumber() > max) {
max = bucket.getSequentialNumber();
}
if (bucket.getState() != WorkBucketStateType.COMPLETE) {
notComplete++;
}
}
if (max == null) {
return 0;
} else {
// what is not listed is assumed to be complete
return max - notComplete;
}
}

@Nullable
public static Integer getExpectedBuckets(TaskType task) {
return null; // TODO task.getWorkState() != null ? task.getWorkState().getNumberOfBuckets() : null;
Expand Down

0 comments on commit 785186e

Please sign in to comment.