diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/DropDownChoicePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/DropDownChoicePanel.java index 4e1ea3f8fc1..972458b5ed0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/DropDownChoicePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/DropDownChoicePanel.java @@ -26,9 +26,11 @@ */ public class DropDownChoicePanel extends InputPanel { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; private static final String ID_INPUT = "input"; + private boolean sortChoices = true; + public DropDownChoicePanel(String id, IModel model, IModel> choices) { this(id, model, choices, false); } @@ -67,7 +69,11 @@ protected String getNullValidDisplayValue() { @Override public IModel> getChoicesModel() { IModel> choices = super.getChoicesModel(); - return Model.ofList(WebComponentUtil.sortDropDownChoices(choices, renderer)); + if (sortChoices) { + return Model.ofList(WebComponentUtil.sortDropDownChoices(choices, renderer)); + } else { + return choices; + } } @Override @@ -102,4 +108,12 @@ public IModel getModel() { protected String getNullValidDisplayValue() { return getString("DropDownChoicePanel.notDefined"); } + + public boolean isSortChoices() { + return sortChoices; + } + + public void setSortChoices(boolean sortChoices) { + this.sortChoices = sortChoices; + } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/PageTraceView.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/PageTraceView.html new file mode 100644 index 00000000000..bbd45847b6a --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/PageTraceView.html @@ -0,0 +1,97 @@ + + + + + + +
+ +
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ + + + + + + +
+
+
+
+

+ + +

+
+
+ + +
+ + diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/PageTraceView.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/PageTraceView.java new file mode 100644 index 00000000000..8b50d28bddd --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/PageTraceView.java @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2020 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.web.page.admin.configuration; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.schema.traces.PerformanceCategory; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import com.evolveum.midpoint.web.application.Url; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.schema.traces.OpNodeTreeBuilder; +import com.evolveum.midpoint.schema.traces.TraceParser; +import com.evolveum.midpoint.schema.traces.visualizer.TraceTreeVisualizer; +import com.evolveum.midpoint.schema.traces.visualizer.TraceVisualizerRegistry; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.security.api.AuthorizationConstants; +import com.evolveum.midpoint.task.api.Task; +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.web.application.AuthorizationAction; +import com.evolveum.midpoint.web.application.PageDescriptor; +import com.evolveum.midpoint.web.component.AceEditor; +import com.evolveum.midpoint.web.component.AjaxSubmitButton; +import com.evolveum.midpoint.web.component.input.DropDownChoicePanel; +import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; +import com.evolveum.midpoint.web.page.admin.dto.TraceViewDto; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * + */ +@Experimental +@PageDescriptor( + urls = { + @Url(mountUrl = "/admin/config/traceView", matchUrlForSecurity = "/admin/config/traceView") + }, action = { + @AuthorizationAction(actionUri = PageAdminConfiguration.AUTH_CONFIGURATION_ALL, + label = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_LABEL, description = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_DESCRIPTION), + @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_TRACE_VIEW_URL, + label = "PageTraceView.auth.view.label", description = "PageTraceView.auth.view.description") +}) +public class PageTraceView extends PageAdminConfiguration { + + private static final Trace LOGGER = TraceManager.getTrace(PageTraceView.class); + + public static final String PARAM_OBJECT_ID = "oid"; + + private static final String DOT_CLASS = PageTraceView.class.getName() + "."; + + private static final String ID_MAIN_FORM = "mainForm"; + + private static final String ID_SHOW = "show"; + private static final String ID_RESULT_LABEL = "resultLabel"; + private static final String ID_RESULT_TEXT = "resultText"; + private static final String ID_CLOCKWORK_EXECUTION = "clockworkExecution"; + private static final String ID_CLOCKWORK_CLICK = "clockworkClick"; + private static final String ID_MAPPING_EVALUATION = "mappingEvaluation"; + private static final String ID_FOCUS_LOAD = "focusLoad"; + private static final String ID_PROJECTION_LOAD = "projectionLoad"; + private static final String ID_FOCUS_CHANGE = "focusChange"; + private static final String ID_PROJECTION_CHANGE = "projectionChange"; + private static final String ID_OTHERS = "others"; + + private static final String ID_SHOW_INVOCATION_ID = "showInvocationId"; + private static final String ID_SHOW_DURATION_BEFORE = "showDurationBefore"; + private static final String ID_SHOW_DURATION_AFTER = "showDurationAfter"; + private static final String ID_SHOW_REPO_OP_COUNT = "showRepoOpCount"; + private static final String ID_SHOW_CONN_ID_OP_COUNT = "showConnIdOpCount"; + private static final String ID_SHOW_REPO_OP_TIME = "showRepoOpTime"; + private static final String ID_SHOW_CONN_ID_OP_TIME = "showConnIdOpTime"; + + private static final String OP_VISUALIZE = DOT_CLASS + "visualize"; + + private transient List parsedOpNodeList; + private transient String parsedFilePath; + + private final Model model = new Model<>(new TraceViewDto()); + + public PageTraceView() { + initLayout(); + } + + private void initLayout() { + Form mainForm = new com.evolveum.midpoint.web.component.form.Form(ID_MAIN_FORM); + add(mainForm); + + DropDownChoicePanel clockworkExecutionChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_CLOCKWORK_EXECUTION, createClockworkLevels(), + new PropertyModel<>(model, TraceViewDto.F_CLOCKWORK_EXECUTION), + this, false); + clockworkExecutionChoice.setSortChoices(false); + clockworkExecutionChoice.setOutputMarkupId(true); + mainForm.add(clockworkExecutionChoice); + + DropDownChoicePanel clockworkClickChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_CLOCKWORK_CLICK, createClockworkLevels(), + new PropertyModel<>(model, TraceViewDto.F_CLOCKWORK_CLICK), + this, false); + clockworkClickChoice.setSortChoices(false); + clockworkClickChoice.setOutputMarkupId(true); + mainForm.add(clockworkClickChoice); + + DropDownChoicePanel mappingEvaluationChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_MAPPING_EVALUATION, createMappingLevels(), + new PropertyModel<>(model, TraceViewDto.F_MAPPING_EVALUATION), + this, false); + mappingEvaluationChoice.setSortChoices(false); + mappingEvaluationChoice.setOutputMarkupId(true); + mainForm.add(mappingEvaluationChoice); + + DropDownChoicePanel focusLoadChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_FOCUS_LOAD, createStandardLevels(), + new PropertyModel<>(model, TraceViewDto.F_FOCUS_LOAD), + this, false); + focusLoadChoice.setSortChoices(false); + focusLoadChoice.setOutputMarkupId(true); + mainForm.add(focusLoadChoice); + + DropDownChoicePanel projectionLoadChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_PROJECTION_LOAD, createStandardLevels(), + new PropertyModel<>(model, TraceViewDto.F_PROJECTION_LOAD), + this, false); + projectionLoadChoice.setSortChoices(false); + projectionLoadChoice.setOutputMarkupId(true); + mainForm.add(projectionLoadChoice); + + DropDownChoicePanel focusChangeChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_FOCUS_CHANGE, createStandardLevels(), + new PropertyModel<>(model, TraceViewDto.F_FOCUS_CHANGE), + this, false); + focusChangeChoice.setSortChoices(false); + focusChangeChoice.setOutputMarkupId(true); + mainForm.add(focusChangeChoice); + + DropDownChoicePanel projectionChangeChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_PROJECTION_CHANGE, createStandardLevels(), + new PropertyModel<>(model, TraceViewDto.F_PROJECTION_CHANGE), + this, false); + projectionChangeChoice.setSortChoices(false); + projectionChangeChoice.setOutputMarkupId(true); + mainForm.add(projectionChangeChoice); + + DropDownChoicePanel otherChoice = WebComponentUtil.createEnumPanel( + GenericTraceVisualizationType.class, ID_OTHERS, createOthersLevels(), + new PropertyModel<>(model, TraceViewDto.F_OTHERS), + this, false); + otherChoice.setSortChoices(false); + otherChoice.setOutputMarkupId(true); + mainForm.add(otherChoice); + + mainForm.add(new CheckBox(ID_SHOW_INVOCATION_ID, new PropertyModel<>(model, TraceViewDto.F_SHOW_INVOCATION_ID))); + mainForm.add(new CheckBox(ID_SHOW_DURATION_BEFORE, new PropertyModel<>(model, TraceViewDto.F_SHOW_DURATION_BEFORE))); + mainForm.add(new CheckBox(ID_SHOW_DURATION_AFTER, new PropertyModel<>(model, TraceViewDto.F_SHOW_DURATION_AFTER))); + mainForm.add(new CheckBox(ID_SHOW_REPO_OP_COUNT, new PropertyModel<>(model, TraceViewDto.F_SHOW_REPO_OP_COUNT))); + mainForm.add(new CheckBox(ID_SHOW_CONN_ID_OP_COUNT, new PropertyModel<>(model, TraceViewDto.F_SHOW_CONN_ID_OP_COUNT))); + mainForm.add(new CheckBox(ID_SHOW_REPO_OP_TIME, new PropertyModel<>(model, TraceViewDto.F_SHOW_REPO_OP_TIME))); + mainForm.add(new CheckBox(ID_SHOW_CONN_ID_OP_TIME, new PropertyModel<>(model, TraceViewDto.F_SHOW_CONN_ID_OP_TIME))); + + AjaxSubmitButton showButton = new AjaxSubmitButton(ID_SHOW, createStringResource("PageTraceView.button.show")) { + @Override + protected void onError(AjaxRequestTarget target) { + target.add(getFeedbackPanel()); + } + + @Override + protected void onSubmit(AjaxRequestTarget target) { + visualize(); + target.add(PageTraceView.this); + } + }; + mainForm.add(showButton); + Label resultLabel = new Label(ID_RESULT_LABEL, (IModel) () -> { + if (!model.getObject().isVisualized()) { + return ""; + } else { + return getString("PageTraceView.trace"); + } + }); + mainForm.add(resultLabel); + + AceEditor resultText = new AceEditor(ID_RESULT_TEXT, new PropertyModel<>(model, TraceViewDto.F_VISUALIZED_TRACE)); + resultText.setReadonly(true); + resultText.setResizeToMaxHeight(true); + resultText.setMode(null); + resultText.add(new VisibleEnableBehaviour() { + @Override + public boolean isVisible() { + return model.getObject().isVisualized(); + } + }); + mainForm.add(resultText); + } + + @NotNull + private IModel> createStandardLevels() { + return (IModel>) () -> + Arrays.asList( + GenericTraceVisualizationType.ONE_LINE, + GenericTraceVisualizationType.BRIEF, + GenericTraceVisualizationType.FULL, + GenericTraceVisualizationType.HIDE); + } + + @NotNull + private IModel> createOthersLevels() { + return (IModel>) () -> + Arrays.asList( + GenericTraceVisualizationType.ONE_LINE, + GenericTraceVisualizationType.HIDE); + } + + @NotNull + private IModel> createClockworkLevels() { + return (IModel>) () -> + Arrays.asList( + GenericTraceVisualizationType.ONE_LINE, + GenericTraceVisualizationType.HIDE); + } + + @NotNull + private IModel> createMappingLevels() { + return (IModel>) () -> + Arrays.asList( + GenericTraceVisualizationType.ONE_LINE, + GenericTraceVisualizationType.BRIEF, + GenericTraceVisualizationType.DETAILED, + GenericTraceVisualizationType.FULL, + GenericTraceVisualizationType.HIDE); + } + + private void visualize() { + Task task = createSimpleTask(OP_VISUALIZE); + OperationResult result = task.getResult(); + + String oid = getPageParameters().get(PARAM_OBJECT_ID).toString(); + LOGGER.info("Visualizing trace from report output {}", oid); + if (oid == null) { + model.getObject().setVisualizedTrace("No report output OID specified"); + return; + } + + try { + // TODO consider using ReportManager to get trace input stream + getSecurityEnforcer().authorize(ModelAuthorizationAction.READ_TRACE.getUrl(), null, + AuthorizationParameters.EMPTY, null, task, result); + + PrismObject reportOutput = getModelService().getObject(ReportOutputType.class, + oid, null, task, result); + String filePath = reportOutput.asObjectable().getFilePath(); + if (filePath != null) { + String visualized = visualizeTrace(filePath); + model.getObject().setVisualizedTrace(visualized); + } else { + throw new SchemaException("No trace file specified in " + reportOutput); + } + } catch (Throwable t) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't visualize trace", t); + result.recordFatalError(t); + model.getObject().setVisualizedTrace("Couldn't visualize trace: " + t.getMessage() + "\n" + ExceptionUtil.printStackTrace(t)); + } finally { + result.computeStatusIfUnknown(); + if (!result.isSuccess()) { + showResult(result); + } + } + } + + private String visualizeTrace(String filePath) throws IOException, SchemaException { + createOpNodeList(filePath); + TraceVisualizationInstructionsType instructions = createVisualizationInstructions(); + parsedOpNodeList.forEach(node -> node.applyVisualizationInstructions(instructions)); + TraceVisualizerRegistry registry = new TraceVisualizerRegistry(getPrismContext()); + TraceTreeVisualizer visualizer = new TraceTreeVisualizer(registry, parsedOpNodeList); + return visualizer.visualize(); + } + + private void createOpNodeList(String filePath) throws IOException, SchemaException { + if (parsedOpNodeList == null || !filePath.equals(parsedFilePath)) { + PrismContext prismContext = getPrismContext(); + TraceParser parser = new TraceParser(prismContext); + TracingOutputType parsed = parser.parse(new File(filePath)); + parsedOpNodeList = new OpNodeTreeBuilder(prismContext).build(parsed); + parsedFilePath = filePath; + } + } + + private TraceVisualizationInstructionsType createVisualizationInstructions() { + TraceViewDto dto = model.getObject(); + TraceVisualizationInstructionsType instructions = new TraceVisualizationInstructionsType(getPrismContext()) + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.CLOCKWORK_EXECUTION) + .end() + .beginVisualization() + .generic(dto.getClockworkExecution()) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.CLOCKWORK_CLICK) + .end() + .beginVisualization() + .generic(dto.getClockworkClick()) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.MAPPING_EVALUATION) + .end() + .beginVisualization() + .generic(dto.getMappingEvaluation()) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.FOCUS_LOAD) + .end() + .beginVisualization() + .generic(dto.getFocusLoad()) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.PROJECTION_LOAD) + .end() + .beginVisualization() + .generic(dto.getProjectionLoad()) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.FOCUS_CHANGE_EXECUTION) + .end() + .beginVisualization() + .generic(dto.getFocusChange()) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.PROJECTION_CHANGE_EXECUTION) + .end() + .beginVisualization() + .generic(dto.getProjectionChange()) + .end() + .end() + .beginInstruction() + .beginVisualization() + .generic(dto.getOthers()) + .end() + .end() + .beginColumns() + .invocationId(dto.isShowInvocationId()) + .durationBefore(dto.isShowDurationBefore()) + .duration(dto.isShowDurationAfter()) + .end(); + + List countFor = instructions.getColumns().getCountFor(); + List timeFor = instructions.getColumns().getTimeFor(); + if (dto.isShowRepoOpCount()) { + countFor.add(PerformanceCategory.REPOSITORY_READ.name()); + countFor.add(PerformanceCategory.REPOSITORY_WRITE.name()); + } + if (dto.isShowConnIdOpCount()) { + countFor.add(PerformanceCategory.ICF_READ.name()); + countFor.add(PerformanceCategory.ICF_WRITE.name()); + } + if (dto.isShowRepoOpTime()) { + timeFor.add(PerformanceCategory.REPOSITORY.name()); + } + if (dto.isShowConnIdOpTime()) { + timeFor.add(PerformanceCategory.ICF.name()); + } + return instructions; + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/dto/TraceViewDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/dto/TraceViewDto.java new file mode 100644 index 00000000000..f88262e87e5 --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/dto/TraceViewDto.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2020 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.web.page.admin.dto; + +import java.io.Serializable; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GenericTraceVisualizationType; + +@Experimental +@SuppressWarnings("unused") +public class TraceViewDto implements Serializable { + + public static final String F_CLOCKWORK_EXECUTION = "clockworkExecution"; + public static final String F_CLOCKWORK_CLICK = "clockworkClick"; + public static final String F_MAPPING_EVALUATION = "mappingEvaluation"; + public static final String F_FOCUS_LOAD = "focusLoad"; + public static final String F_PROJECTION_LOAD = "projectionLoad"; + public static final String F_FOCUS_CHANGE = "focusChange"; + public static final String F_PROJECTION_CHANGE = "projectionChange"; + public static final String F_OTHERS = "others"; + + public static final String F_SHOW_INVOCATION_ID = "showInvocationId"; + public static final String F_SHOW_DURATION_BEFORE = "showDurationBefore"; + public static final String F_SHOW_DURATION_AFTER = "showDurationAfter"; + public static final String F_SHOW_REPO_OP_COUNT = "showRepoOpCount"; + public static final String F_SHOW_CONN_ID_OP_COUNT = "showConnIdOpCount"; + public static final String F_SHOW_REPO_OP_TIME = "showRepoOpTime"; + public static final String F_SHOW_CONN_ID_OP_TIME = "showConnIdOpTime"; + + public static final String F_VISUALIZED_TRACE = "visualizedTrace"; + + private GenericTraceVisualizationType clockworkExecution = GenericTraceVisualizationType.ONE_LINE; + private GenericTraceVisualizationType clockworkClick = GenericTraceVisualizationType.ONE_LINE; + private GenericTraceVisualizationType mappingEvaluation = GenericTraceVisualizationType.ONE_LINE; + private GenericTraceVisualizationType focusLoad = GenericTraceVisualizationType.ONE_LINE; + private GenericTraceVisualizationType projectionLoad = GenericTraceVisualizationType.ONE_LINE; + private GenericTraceVisualizationType focusChange = GenericTraceVisualizationType.ONE_LINE; + private GenericTraceVisualizationType projectionChange = GenericTraceVisualizationType.ONE_LINE; + private GenericTraceVisualizationType others = GenericTraceVisualizationType.HIDE; + + private boolean showInvocationId; + private boolean showDurationBefore; + private boolean showDurationAfter; + private boolean showRepoOpCount; + private boolean showConnIdOpCount; + private boolean showRepoOpTime; + private boolean showConnIdOpTime; + + private transient String visualizedTrace; + + public TraceViewDto() { + } + + public GenericTraceVisualizationType getClockworkExecution() { + return clockworkExecution; + } + + public void setClockworkExecution(GenericTraceVisualizationType clockworkExecution) { + this.clockworkExecution = clockworkExecution; + } + + public GenericTraceVisualizationType getClockworkClick() { + return clockworkClick; + } + + public void setClockworkClick(GenericTraceVisualizationType clockworkClick) { + this.clockworkClick = clockworkClick; + } + + public GenericTraceVisualizationType getMappingEvaluation() { + return mappingEvaluation; + } + + public void setMappingEvaluation(GenericTraceVisualizationType mappingEvaluation) { + this.mappingEvaluation = mappingEvaluation; + } + + public GenericTraceVisualizationType getFocusLoad() { + return focusLoad; + } + + public void setFocusLoad(GenericTraceVisualizationType focusLoad) { + this.focusLoad = focusLoad; + } + + public GenericTraceVisualizationType getProjectionLoad() { + return projectionLoad; + } + + public void setProjectionLoad(GenericTraceVisualizationType projectionLoad) { + this.projectionLoad = projectionLoad; + } + + public GenericTraceVisualizationType getFocusChange() { + return focusChange; + } + + public void setFocusChange(GenericTraceVisualizationType focusChange) { + this.focusChange = focusChange; + } + + public GenericTraceVisualizationType getProjectionChange() { + return projectionChange; + } + + public void setProjectionChange(GenericTraceVisualizationType projectionChange) { + this.projectionChange = projectionChange; + } + + public GenericTraceVisualizationType getOthers() { + return others; + } + + public void setOthers(GenericTraceVisualizationType others) { + this.others = others; + } + + public boolean isShowInvocationId() { + return showInvocationId; + } + + public void setShowInvocationId(boolean showInvocationId) { + this.showInvocationId = showInvocationId; + } + + public boolean isShowDurationBefore() { + return showDurationBefore; + } + + public void setShowDurationBefore(boolean showDurationBefore) { + this.showDurationBefore = showDurationBefore; + } + + public boolean isShowDurationAfter() { + return showDurationAfter; + } + + public void setShowDurationAfter(boolean showDurationAfter) { + this.showDurationAfter = showDurationAfter; + } + + public boolean isShowRepoOpCount() { + return showRepoOpCount; + } + + public void setShowRepoOpCount(boolean showRepoOpCount) { + this.showRepoOpCount = showRepoOpCount; + } + + public boolean isShowConnIdOpCount() { + return showConnIdOpCount; + } + + public void setShowConnIdOpCount(boolean showConnIdOpCount) { + this.showConnIdOpCount = showConnIdOpCount; + } + + public boolean isShowRepoOpTime() { + return showRepoOpTime; + } + + public void setShowRepoOpTime(boolean showRepoOpTime) { + this.showRepoOpTime = showRepoOpTime; + } + + public boolean isShowConnIdOpTime() { + return showConnIdOpTime; + } + + public void setShowConnIdOpTime(boolean showConnIdOpTime) { + this.showConnIdOpTime = showConnIdOpTime; + } + + public void setVisualizedTrace(String visualizedTrace) { + this.visualizedTrace = visualizedTrace; + } + + public String getVisualizedTrace() { + return visualizedTrace; + } + + public boolean isVisualized() { + return visualizedTrace != null; + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java index 38e2768e512..176cc6126d0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java @@ -26,6 +26,9 @@ import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; +import com.evolveum.midpoint.web.page.admin.configuration.PageDebugView; +import com.evolveum.midpoint.web.page.admin.configuration.PageEvaluateMapping; +import com.evolveum.midpoint.web.page.admin.configuration.PageTraceView; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.StringUtils; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -355,6 +358,7 @@ public String getButtonIconCssClass() { } }); + boolean canViewTraces; boolean canReadTraces; try { canReadTraces = isAuthorized(ModelAuthorizationAction.READ_TRACE.getUrl()); @@ -363,6 +367,8 @@ public String getButtonIconCssClass() { canReadTraces = false; } + canViewTraces = canReadTraces && WebModelServiceUtils.isEnableExperimentalFeature(this); + ButtonInlineMenuItem item = new ButtonInlineMenuItem(createStringResource("DownloadButtonPanel.download")) { private static final long serialVersionUID = 1L; @@ -378,7 +384,6 @@ public void onClick(AjaxRequestTarget target) { downloadPerformed(target, rowDto.getValue(), ajaxDownloadBehavior); } }; - } @Override @@ -396,6 +401,37 @@ public boolean isHeaderMenuItem(){ } menuItems.add(item); + ButtonInlineMenuItem viewTraceItem = new ButtonInlineMenuItem(createStringResource("DownloadButtonPanel.viewTrace")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + SelectableBeanImpl rowDto = getRowModel().getObject(); + currentReport = rowDto.getValue(); + PageParameters parameters = new PageParameters(); + parameters.add(PageTraceView.PARAM_OBJECT_ID, currentReport.getOid()); + navigateToNext(PageTraceView.class, parameters); + } + }; + } + + @Override + public String getButtonIconCssClass() { + return "fa fa-eye"; + } + + @Override + public boolean isHeaderMenuItem() { + return false; + } + }; + menuItems.add(viewTraceItem); + viewTraceItem.setVisibilityChecker((rowModel, isHeader) -> canViewTraces && isTrace(rowModel)); return menuItems; } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/DictionaryExpander.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/DictionaryExpander.java new file mode 100644 index 00000000000..880dd805d75 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/DictionaryExpander.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.Visitable; +import com.evolveum.midpoint.prism.Visitor; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TraceDictionaryEntryType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TraceDictionaryType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingOutputType; + +/** + * Expands object references (from pointers to dictionary to full objects). + */ +@Experimental +public class DictionaryExpander { + + private static final Trace LOGGER = TraceManager.getTrace(DictionaryExpander.class); + + private final TracingOutputType tracingOutput; + + DictionaryExpander(TracingOutputType tracingOutput) { + this.tracingOutput = tracingOutput; + } + + public void expand() { + long start = System.currentTimeMillis(); + if (tracingOutput != null && tracingOutput.getResult() != null) { + expandDictionary(tracingOutput.getResult(), new ExpandingVisitor(tracingOutput.getDictionary())); + } + LOGGER.info("Dictionary expanded in {} milliseconds", System.currentTimeMillis() - start); + } + + @SuppressWarnings("rawtypes") + private static class ExpandingVisitor implements Visitor { + + private final TraceDictionaryType dictionary; + + private ExpandingVisitor(TraceDictionaryType dictionary) { + this.dictionary = dictionary; + } + + @Override + public void visit(Visitable visitable) { + if (visitable instanceof PrismReferenceValue) { + PrismReferenceValue refVal = (PrismReferenceValue) visitable; + if (refVal.getObject() == null && refVal.getOid() != null && refVal.getOid().startsWith(SchemaConstants.TRACE_DICTIONARY_PREFIX)) { + String id = refVal.getOid().substring(SchemaConstants.TRACE_DICTIONARY_PREFIX.length()); + TraceDictionaryEntryType entry = findEntry(id); + if (entry == null) { + LOGGER.error("No dictionary entry #{}", id); + } else if (entry.getObject() == null) { + LOGGER.error("No object in dictionary entry #{}", id); + } else if (entry.getObject().asReferenceValue().getObject() == null) { + LOGGER.error("No embedded object in dictionary entry #{}", id); + } else { + PrismObject object = entry.getObject().asReferenceValue().getObject(); + refVal.setObject(object); + refVal.setOid(object.getOid()); + } + } + } + } + + private TraceDictionaryEntryType findEntry(String id) { + for (TraceDictionaryEntryType entry : dictionary.getEntry()) { + String qualifiedId = entry.getOriginDictionaryId() + ":" + entry.getIdentifier(); + if (qualifiedId.equals(id)) { + return entry; + } + } + return null; + } + } + + private void expandDictionary(OperationResultType resultBean, ExpandingVisitor expandingVisitor) { + resultBean.getTrace().forEach(trace -> trace.asPrismContainerValue().accept(expandingVisitor)); + resultBean.getPartialResults().forEach(partialResult -> expandDictionary(partialResult, expandingVisitor)); + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpNode.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpNode.java new file mode 100644 index 00000000000..38b2f7f2806 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpNode.java @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2020 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.traces; + +import java.util.*; +import java.util.stream.Collectors; +import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.prism.ComplexTypeDefinition; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.RawType; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; + +@Experimental +public class OpNode { + + private final PrismContext prismContext; + private final OperationResultType result; + private final List children = new ArrayList<>(); + private final OpNode parent; + private final OpResultInfo info; + private final TraceInfo traceInfo; + + private TraceVisualizationInstructionsType visualizationInstructions; + private TraceVisualizationInstructionType visualizationInstruction; + private boolean stop = false; + private boolean visible = true; + + public OpNode(PrismContext prismContext, OperationResultType result, OpResultInfo info, OpNode parent, TraceInfo traceInfo) { + this.prismContext = prismContext; + assert result != null; + this.result = result; + this.info = info; + this.parent = parent; + this.traceInfo = traceInfo; + } + + public OperationResultType getResult() { + return result; + } + + public OperationsPerformanceInformationType getPerformance() { + return info.getPerformance(); + } + + public Map getPerformanceByCategory() { + return info.getPerformanceByCategory(); + } + + public OpType getType() { + return info.getType(); + } + + public OperationKindType getKind() { + return info.getKind(); + } + + public List getChildren() { + return children; + } + + public OpNode getParent() { + return parent; + } + + public long getStart(long base) { + return XmlTypeConverter.toMillis(result.getStart()) - base; + } + + public String dump() { + try { + return OperationResult.createOperationResult(result).debugDump(); + } catch (RuntimeException e) { + e.printStackTrace(); + return e.getMessage(); + } + } + + public TraceType getFirstTrace() { + return result.getTrace().isEmpty() ? null : result.getTrace().get(0); + } + + public String getOperationQualified() { + StringBuilder sb = new StringBuilder(); + sb.append(result.getOperation()); + if (!result.getQualifier().isEmpty()) { + sb.append(" ("); + sb.append(String.join("; ", result.getQualifier())); + sb.append(")"); + } + return sb.toString(); + } + + public String getOperationNameFormatted() { + return getType().getFormattedName(this) + (visible ? "" : "!"); + } + + public String getClockworkState() { + ClockworkTraceType click = getTrace(ClockworkTraceType.class); + if (click instanceof ClockworkClickTraceType && ((ClockworkClickTraceType) click).getState() != null) { + return String.valueOf(((ClockworkClickTraceType) click).getState()); + } else if (click != null && click.getInputLensContext() != null && click.getInputLensContext().getState() != null) { + return String.valueOf(click.getInputLensContext().getState()); + } else if (parent != null) { + return parent.getClockworkState(); + } else { + return ""; + } + } + + public String getExecutionWave() { + ClockworkTraceType click = getTrace(ClockworkTraceType.class); + if (click instanceof ClockworkClickTraceType && ((ClockworkClickTraceType) click).getExecutionWave() != null) { + return String.valueOf(((ClockworkClickTraceType) click).getExecutionWave()); + } else if (click != null && click.getInputLensContext() != null && click.getInputLensContext().getExecutionWave() != null) { + return String.valueOf(click.getInputLensContext().getExecutionWave()); + } else if (parent != null) { + return parent.getExecutionWave(); + } else { + return ""; + } + } + + public String getProjectionWave() { + ClockworkTraceType click = getTrace(ClockworkTraceType.class); + if (click instanceof ClockworkClickTraceType && ((ClockworkClickTraceType) click).getProjectionWave() != null) { + return String.valueOf(((ClockworkClickTraceType) click).getProjectionWave()); + } else if (click != null && click.getInputLensContext() != null && click.getInputLensContext().getProjectionWave() != null) { + return String.valueOf(click.getInputLensContext().getProjectionWave()); + } else if (parent != null) { + return parent.getProjectionWave(); + } else { + return ""; + } + } + + public T getTrace(Class aClass) { + return TraceUtil.getTrace(result, aClass); + } + + public T getTraceUpwards(Class aClass) { + T trace = getTrace(aClass); + if (trace != null || parent == null) { + return trace; + } else { + return getTraceUpwards(aClass); + } + } + + public T getTraceDownwards(Class aClass) { + T trace = getTrace(aClass); + if (trace != null) { + return trace; + } else { + for (OpNode child : children) { + T inChild = child.getTraceDownwards(aClass); + if (inChild != null) { + return inChild; + } + } + return null; + } + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public void applyVisualizationInstructions(@NotNull TraceVisualizationInstructionsType instructions) { + applyLocalVisualizationInstruction(instructions); + children.forEach(child -> child.applyVisualizationInstructions(instructions)); + } + + private void applyLocalVisualizationInstruction(@NotNull TraceVisualizationInstructionsType instructions) { + visualizationInstructions = instructions; + if (parent != null && parent.stop) { + visualizationInstruction = null; + visible = false; + stop = true; + } else { + visualizationInstruction = findApplicableInstruction(instructions); + visible = visualizationInstruction != null && visualizationInstruction.getVisualization() != null + && isVisible(visualizationInstruction.getVisualization()); + } + } + + private static boolean isVisible(@NotNull TraceVisualizationType visualization) { + return visualization.getGeneric() != null && visualization.getGeneric() != GenericTraceVisualizationType.HIDE + && visualization.getGeneric() != GenericTraceVisualizationType.STOP; + } + + private TraceVisualizationInstructionType findApplicableInstruction(@NotNull TraceVisualizationInstructionsType instructions) { + for (TraceVisualizationInstructionType instruction : instructions.getInstruction()) { + if (matches(instruction)) { + return instruction; + } + } + return null; + } + + private boolean matches(TraceVisualizationInstructionType instruction) { + if (instruction.getSelector().isEmpty()) { + return true; + } else { + for (TraceSelectorType selector : instruction.getSelector()) { + if (matches(selector)) { + return true; + } + } + return false; + } + } + + private boolean matches(TraceSelectorType selector) { + return matchesTraceType(selector.getTraceType()) && matchesOperationKind(selector.getOperationKind()); + } + + private boolean matchesOperationKind(OperationKindType operationKind) { + return getResult().getOperationKind() == operationKind; + } + + private boolean matchesTraceType(List typeList) { + if (typeList.isEmpty()) { + return true; + } else { + for (QName traceType : typeList) { + if (matchesTraceType(traceType)) { + return true; + } + } + return false; + } + } + + private boolean matchesTraceType(QName traceType) { + ComplexTypeDefinition ctd = prismContext.getSchemaRegistry().findComplexTypeDefinitionByType(traceType); + if (ctd == null) { + throw new IllegalStateException("No complex type definition for '" + traceType + "'"); + } + Class traceClass = ctd.getCompileTimeClass(); + if (traceClass == null) { + throw new IllegalStateException("Trace type '" + traceType + "' has no compile time class: " + ctd); + } + for (TraceType trace : result.getTrace()) { + if (traceClass.isAssignableFrom(trace.getClass())) { + return true; + } + } + return false; + } + + private boolean matchesOperationKind(Collection kindList) { + if (kindList.isEmpty()) { + return true; + } else { + for (OperationKindType kind : kindList) { + if (matchesOperationKind(kind)) { + return true; + } + } + return false; + } + } + + + public void applyOptions(Options options) { + setVisible(isVisible(options)); + for (OpNode child : children) { + child.applyOptions(options); + } + } + + private boolean isVisible(Options options) { + if (options.getTypesToShow().contains(getType())) { + return true; + } + if (options.getKindsToShow().contains(getKind())) { + return true; + } + for (PerformanceCategory cat : options.getCategoriesToShow()) { + PerformanceCategoryInfo perfInfo = getPerformanceByCategory().get(cat); + if (options.isShowAlsoParents()) { + if (perfInfo.getTotalCount() > 0) { + return true; + } + } else { + if (perfInfo.getOwnCount() > 0) { + return true; + } + } + } + return false; + } + public String getImportanceSymbol() { + if (result.getImportance() != null) { + switch (result.getImportance()) { + case MAJOR: return "O"; + case NORMAL: return "o"; + case MINOR: return "."; + default: return "?"; + } + } else return Boolean.TRUE.equals(result.isMinor()) ? "." : "o"; + } + + public LensContextType getContextToView() { + List traces = result.getTrace(); + for (TraceType trace : traces) { + if (trace instanceof ClockworkTraceType) { + return ((ClockworkTraceType) trace).getOutputLensContext(); + } + } + return null; + } + + public List getObjectsToView() { + List traces = result.getTrace(); + for (TraceType trace : traces) { + if (trace instanceof ClockworkTraceType) { + return processContext(((ClockworkTraceType) trace).getOutputLensContext()); + } + } + return null; + } + public List processContext(LensContextType ctx) { + List rv = new ArrayList(); + if (ctx != null && ctx.getFocusContext() != null) { + LensFocusContextType fctx = ctx.getFocusContext(); + ObjectType objectOld = fctx.getObjectOld(); + ObjectType objectCurrent = fctx.getObjectCurrent(); + ObjectType objectNew = fctx.getObjectNew(); + if (objectOld != null) { + rv.add(new ViewedObject("old", objectOld.asPrismObject())); + } + if (objectCurrent != null) { + rv.add(new ViewedObject("current", objectCurrent.asPrismObject())); + } + if (objectNew != null) { + rv.add(new ViewedObject("new", objectNew.asPrismObject())); + } + } + return rv.isEmpty() ? null : rv; + } + public List getTraceNames() { + return result.getTrace().stream().map(trace -> trace.getClass().getSimpleName()).collect(Collectors.toList()); + } + public String getResultComment() { + return getResultComment(result); + } + public static String getResultComment(OperationResultType result) { + ParamsType returns = result.getReturns(); + for (EntryType entry : returns.getEntry()) { + if (OperationResult.RETURN_COMMENT.equals(entry.getKey())) { + JAXBElement value = entry.getEntryValue(); + if (value == null) { + return null; + } else if (value.getValue() instanceof RawType) { + return ((RawType) value.getValue()).extractString(); + } else { + return String.valueOf(value.getValue()); + } + } + } + return null; + } + + public int getLogEntriesCount() { + int rv = 0; + for (LogSegmentType segment : result.getLog()) { + rv += segment.getEntry().size(); + } + return rv; + } + + public TraceInfo getTraceInfo() { + return traceInfo; + } + + public Double getOverhead() { + long repository = getPerformanceByCategory().get(PerformanceCategory.REPOSITORY).getTotalTime(); + long icf = getPerformanceByCategory().get(PerformanceCategory.ICF).getTotalTime(); + Long total = getResult().getMicroseconds(); + if (total != null && total.doubleValue() != 0.0) { + return (total.doubleValue() - repository - icf) / total.doubleValue(); + } else { + return null; + } + } + + public Double getOverhead2() { + long repository = getPerformanceByCategory().get(PerformanceCategory.REPOSITORY_CACHE).getTotalTime(); + long icf = getPerformanceByCategory().get(PerformanceCategory.ICF).getTotalTime(); + Long total = getResult().getMicroseconds(); + if (total != null && total.doubleValue() != 0.0) { + return (total.doubleValue() - repository - icf) / total.doubleValue(); + } else { + return null; + } + } + + public TraceVisualizationInstructionType getVisualizationInstruction() { + return visualizationInstruction; + } + + public GenericTraceVisualizationType getGenericVisualization() { + if (visualizationInstruction != null && visualizationInstruction.getVisualization() != null) { + return visualizationInstruction.getVisualization().getGeneric(); + } else { + return null; + } + } + + public TraceDataSelectionType getDataSelection() { + if (visualizationInstruction != null && visualizationInstruction.getVisualization() != null) { + return visualizationInstruction.getVisualization().getData(); + } else { + return null; + } + } + + public String getFocusName() { + ClockworkRunTraceType trace = getTraceUpwards(ClockworkRunTraceType.class); + return trace != null ? trace.getFocusName() : null; + } + + public boolean isShowInvocationId() { + return visualizationInstructions.getColumns() != null && + Boolean.TRUE.equals(visualizationInstructions.getColumns().isInvocationId()); + } + + public boolean isShowDurationBefore() { + return visualizationInstructions.getColumns() != null && + Boolean.TRUE.equals(visualizationInstructions.getColumns().isDurationBefore()); + } + + public boolean isShowDurationAfter() { + return visualizationInstructions.getColumns() != null && + Boolean.TRUE.equals(visualizationInstructions.getColumns().isDuration()); + } + + public Long getInvocationId() { + return result.getInvocationId(); + } + + public String getMillisecondsFormatted() { + if (result.getMicroseconds() != null) { + return String.format(Locale.US, "%.1f", result.getMicroseconds() / 1000.0); + } else { + return ""; + } + } + + public Double getMilliseconds() { + return result.getMicroseconds() != null ? result.getMicroseconds() / 1000.0 : null; + } + + public List getCountColumns() { + if (visualizationInstructions.getColumns() != null) { + return parse(visualizationInstructions.getColumns().getCountFor()); + } else { + return Collections.emptyList(); + } + } + + public List getTimeColumns() { + if (visualizationInstructions.getColumns() != null) { + return parse(visualizationInstructions.getColumns().getTimeFor()); + } else { + return Collections.emptyList(); + } + } + + private List parse(List names) { + return names.stream() + .map(PerformanceCategory::valueOf) + .collect(Collectors.toList()); + } + + public List> getCounts() { + return getSelectedInformation(getCountColumns()); + } + + public List> getTimes() { + return getSelectedInformation(getTimeColumns()); + } + + @NotNull + private List> getSelectedInformation(List categories) { + Map all = getPerformanceByCategory(); + List> selected = new ArrayList<>(); + for (PerformanceCategory category : categories) { + PerformanceCategoryInfo info = all.get(category); + if (info != null) { + PerformanceCategoryInfo adapted = adaptPerformanceInfo(category, info); + selected.add(new ImmutablePair<>(category, adapted)); + } + } + return selected; + } + + /** + * Computes "adapted own" information: subtracts all visible "own" data from children from the totals, leading to + * apparent own information. + */ + @NotNull + private PerformanceCategoryInfo adaptPerformanceInfo(PerformanceCategory category, PerformanceCategoryInfo info) { + PerformanceCategoryInfo adapted = new PerformanceCategoryInfo(); + adapted.setTotalCount(info.getTotalCount()); + adapted.setOwnCount(info.getTotalCount()); + adapted.setTotalTime(info.getTotalTime()); + adapted.setOwnTime(info.getTotalTime()); + children.forEach(child -> child.subtractOwnVisiblePerformanceInfo(category, adapted)); + return adapted; + } + + private void subtractOwnVisiblePerformanceInfo(PerformanceCategory category, PerformanceCategoryInfo adapted) { + if (visible) { + PerformanceCategoryInfo info = getPerformanceByCategory().get(category); + if (info != null) { + adapted.setOwnCount(adapted.getOwnCount() - info.getOwnCount()); + adapted.setOwnTime(adapted.getOwnTime() - info.getOwnTime()); + } + } + children.forEach(child -> child.subtractOwnVisiblePerformanceInfo(category, adapted)); + } + + public boolean showTotals() { + // temporary implementation + OperationKindType kind = getKind(); + return kind == OperationKindType.CLOCKWORK_EXECUTION || kind == OperationKindType.CLOCKWORK_CLICK; + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpNodeTreeBuilder.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpNodeTreeBuilder.java new file mode 100644 index 00000000000..8e2220ccb8c --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpNodeTreeBuilder.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingOutputType; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; + +@Experimental +public class OpNodeTreeBuilder { + + @NotNull private final PrismContext prismContext; + + private IdentityHashMap infoMap = new IdentityHashMap<>(); + + public OpNodeTreeBuilder(PrismContext prismContext) { + this.prismContext = prismContext; + } + + public List build(TracingOutputType tracingOutput) { + List rv = new ArrayList<>(); + addNode(null, rv, tracingOutput.getResult(), new TraceInfo(tracingOutput)); + return rv; + + } + + private void addNode(OpNode parent, List rv, OperationResultType result, TraceInfo traceInfo) { + OpResultInfo info = OpResultInfo.create(result, infoMap); + OpNode newNode = new OpNode(prismContext, result, info, parent, traceInfo); + rv.add(newNode); + for (OperationResultType child : result.getPartialResults()) { + addNode(newNode, newNode.getChildren(), child, traceInfo); + } + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpResultInfo.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpResultInfo.java new file mode 100644 index 00000000000..b5aa259215d --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpResultInfo.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020 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.traces; + +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; + +import com.evolveum.midpoint.schema.statistics.OperationsPerformanceInformationUtil; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationsPerformanceInformationType; + +@Experimental +public class OpResultInfo { + private OperationResultType result; + private OpType type; + private OperationsPerformanceInformationType performance; + private final Map performanceByCategory = new HashMap<>(); + + public OperationResultType getResult() { + return result; + } + + public void setResult(OperationResultType result) { + this.result = result; + } + + public OpType getType() { + return type; + } + + public OperationKindType getKind() { + return result.getOperationKind(); + } + + public void setType(OpType type) { + this.type = type; + } + + public OperationsPerformanceInformationType getPerformance() { + return performance; + } + + public void setPerformance(OperationsPerformanceInformationType performance) { + this.performance = performance; + } + + public Map getPerformanceByCategory() { + return performanceByCategory; + } + + public static OpResultInfo create(OperationResultType result, IdentityHashMap infoMap) { + OpResultInfo info = new OpResultInfo(); + info.result = result; + info.type = OpType.determine(result); + info.performance = getPerformance(result); + info.determinePerformanceByCategory(infoMap); + + infoMap.put(result, info); + return info; + } + + private void determinePerformanceByCategory(IdentityHashMap infoMap) { + for (PerformanceCategory category : PerformanceCategory.values()) { + if (!category.isDerived()) { + PerformanceCategoryInfo info = determinePerformanceForCategory(category, infoMap); + performanceByCategory.put(category, info); + } + } + for (PerformanceCategory category : PerformanceCategory.values()) { + if (category.isDerived()) { + PerformanceCategoryInfo info = determinePerformanceForCategory(category, infoMap); + performanceByCategory.put(category, info); + } + } + } + + private PerformanceCategoryInfo determinePerformanceForCategory(PerformanceCategory category, + IdentityHashMap infoMap) { + PerformanceCategoryInfo rv = new PerformanceCategoryInfo(); + int count = 0; + long time = 0; + if (category.isDerived()) { + for (PerformanceCategory plus : category.getPlus()) { + count += performanceByCategory.get(plus).getOwnCount(); + time += performanceByCategory.get(plus).getOwnTime(); + } + for (PerformanceCategory minus : category.getMinus()) { + count -= performanceByCategory.get(minus).getOwnCount(); + time -= performanceByCategory.get(minus).getOwnTime(); + } + } else { + boolean matches = category.matches(result); + if (matches) { + count = 1; + time = result.getMicroseconds() != null ? result.getMicroseconds() : 0; + } + } + rv.setOwnCount(count); + rv.setOwnTime(time); + rv.setTotalCount(count); + rv.setTotalTime(time); + for (OperationResultType subresult : result.getPartialResults()) { + OpResultInfo subInfo = getInfo(subresult, infoMap); + PerformanceCategoryInfo subPerf = subInfo.getPerformanceFor(category); + rv.setTotalCount(rv.getTotalCount() + subPerf.getTotalCount()); + rv.setTotalTime(rv.getTotalTime() + subPerf.getTotalTime()); + } + return rv; + } + + private OpResultInfo getInfo(OperationResultType result, IdentityHashMap infoMap) { + OpResultInfo info = infoMap.get(result); + if (info != null) { + return info; + } else { + return create(result, infoMap); + } + } + + private PerformanceCategoryInfo getPerformanceFor(PerformanceCategory category) { + return performanceByCategory.get(category); + + } + private static OperationsPerformanceInformationType getPerformance(OperationResultType result) { + OperationsPerformanceInformationType rv = new OperationsPerformanceInformationType(); + addPerformance(rv, result); + return rv; + } + + private static void addPerformance(OperationsPerformanceInformationType rv, OperationResultType result) { + if (result.getMicroseconds() != null) { + OperationsPerformanceInformationType oper = new OperationsPerformanceInformationType(); + oper.beginOperation() + .name(result.getOperation()) + .invocationCount(1) + .totalTime(result.getMicroseconds()) + .minTime(result.getMicroseconds()) + .maxTime(result.getMicroseconds()); + OperationsPerformanceInformationUtil.addTo(rv, oper); + } + for (OperationResultType child : result.getPartialResults()) { + addPerformance(rv, child); + } + } + + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpType.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpType.java new file mode 100644 index 00000000000..70b2c0993d7 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OpType.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2020 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.traces; + +import java.util.ArrayList; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.apache.commons.lang3.StringUtils; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; + +import static com.evolveum.midpoint.schema.traces.TraceUtil.*; + +@Experimental +public enum OpType { + + CLOCKWORK_RUN(OperationKindType.CLOCKWORK_EXECUTION, "Clockwork run", "com.evolveum.midpoint.model.impl.lens.Clockwork.run"), + CLOCKWORK_CLICK(OperationKindType.CLOCKWORK_CLICK, "Clockwork click", "com.evolveum.midpoint.model.impl.lens.Clockwork.click"), + PROJECTOR_PROJECT(OperationKindType.PROJECTOR_EXECUTION, "Projector project", "com.evolveum.midpoint.model.impl.lens.projector.Projector.project"), + PROJECTOR_COMPONENT(OperationKindType.OTHER,"Projector component", + "com.evolveum.midpoint.model.impl.lens.projector.Projector.*"), + CLOCKWORK_METHOD(OperationKindType.OTHER,"Clockwork method", + "com.evolveum.midpoint.model.impl.lens.Clockwork.*"), + MAPPING_EVALUATION(OperationKindType.MAPPING_EVALUATION,"Mapping evaluation", + "com.evolveum.midpoint.model.common.mapping.MappingImpl.evaluate"), + MAPPING_PREPARATION(OperationKindType.OTHER,"Mapping preparation", + "com.evolveum.midpoint.model.common.mapping.MappingImpl.prepare"), + MAPPING_EVALUATION_PREPARED (OperationKindType.OTHER,"Prepared mapping evaluation", + "com.evolveum.midpoint.model.common.mapping.MappingImpl.evaluatePrepared"), + SCRIPT_EXECUTION (OperationKindType.SCRIPT_EVALUATION, "Script evaluation", + "xxxx"), + CHANGE_EXECUTION (OperationKindType.OTHER,"Change execution", + "com.evolveum.midpoint.model.impl.lens.ChangeExecutor.execute"), + FOCUS_CHANGE_EXECUTION (OperationKindType.FOCUS_CHANGE_EXECUTION,"Focus change execution", + "com.evolveum.midpoint.model.impl.lens.ChangeExecutor.execute.focus.*"), + PROJECTION_CHANGE_EXECUTION (OperationKindType.PROJECTION_CHANGE_EXECUTION,"Projection change execution", + "com.evolveum.midpoint.model.impl.lens.ChangeExecutor.execute.projection.*"), + CHANGE_EXECUTION_DELTA (OperationKindType.OTHER,"Change execution - delta", + "com.evolveum.midpoint.model.impl.lens.ChangeExecutor.executeDelta"), + CHANGE_EXECUTION_OTHER (OperationKindType.OTHER,"Change execution - other", + "com.evolveum.midpoint.model.impl.lens.ChangeExecutor.*"), + FOCUS_LOAD (OperationKindType.FOCUS_LOAD,"Focus load", + result -> isLoadedFromRepository(result), + "com.evolveum.midpoint.model.impl.lens.projector.ContextLoader.determineFocusContext"), + FOCUS_LOAD_CHECK (OperationKindType.FOCUS_LOAD_CHECK,"Focus load check", + "com.evolveum.midpoint.model.impl.lens.projector.ContextLoader.determineFocusContext"), + SHADOW_LOAD (OperationKindType.OTHER,"Shadow load", + "com.evolveum.midpoint.model.impl.lens.projector.ContextLoader.loadProjection"), + FULL_PROJECTION_LOAD (OperationKindType.FULL_PROJECTION_LOAD, "Full projection load", + "com.evolveum.midpoint.model.impl.lens.projector.ContextLoader.loadFullShadow"), + MODEL_OTHER (OperationKindType.OTHER,"Model - other", + "com.evolveum.midpoint.model.*"), + PROVISIONING_API (OperationKindType.PROVISIONING,"Provisioning (API)", "com.evolveum.midpoint.provisioning.api.*"), + PROVISIONING_INTERNAL (OperationKindType.OTHER,"Provisioning (internal)", "com.evolveum.midpoint.provisioning.impl.*"), + REPOSITORY (OperationKindType.REPOSITORY, "Repository", "com.evolveum.midpoint.repo.api.RepositoryService.*"), + REPOSITORY_CACHE (OperationKindType.REPOSITORY_CACHE, "Repository cache", "com.evolveum.midpoint.repo.cache.RepositoryCache.*"), + CONNECTOR (OperationKindType.CONNECTOR, "Connector", "com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance.*"), + OTHER (OperationKindType.OTHER, "Other", + "*"); + + private static final String LOADED_FROM_REPOSITORY = "Loaded from repository"; // TODO + + private final OperationKindType kind; + private final String label; + private final Function predicate; + @SuppressWarnings("unused") + private final List patterns; + private final List compiledPatterns; + + OpType(OperationKindType kind, String label, Function predicate, String... patterns) { + this.kind = kind; + this.label = label; + this.predicate = predicate; + this.patterns = Arrays.asList(patterns); + this.compiledPatterns = new ArrayList<>(); + for (String pattern : patterns) { + String regex = toRegex(pattern); + compiledPatterns.add(Pattern.compile(regex)); + } + } + + private static boolean isLoadedFromRepository(OperationResultType result) { + return LOADED_FROM_REPOSITORY.equals(OpNode.getResultComment(result)); + } + + OpType(OperationKindType kind, String label, String... patterns) { + this(kind, label, null, patterns); + } + + public String getFormattedName(OpNode node) { + OperationResultType opResult = node.getResult(); + String operation = opResult.getOperation(); + String last = getLast(operation); + String qualifiers = String.join("; ", opResult.getQualifier()); + String commaQualifiers = qualifiers.isEmpty() ? "" : " - " + qualifiers; + + if ("com.evolveum.midpoint.model.impl.lens.projector.ConsolidationProcessor.consolidateItem".equals(operation)) { + return "Consolidating " + getParameter(opResult, "itemPath"); + } else if ("com.evolveum.midpoint.model.common.expression.evaluator.AbstractValueTransformationExpressionEvaluator.processValuesCombination".equals(operation)) { + return "Processing value combination"; + } else if ("com.evolveum.midpoint.model.common.expression.evaluator.AbstractValueTransformationExpressionEvaluator.evaluateScriptExpression".equals(operation) || + "com.evolveum.midpoint.model.common.expression.evaluator.AbstractValueTransformationExpressionEvaluator.evaluateExpression".equals(operation)) { + return "Evaluate: " + getContext(opResult, "context"); + } else if ("com.evolveum.midpoint.model.common.expression.script.ScriptExpression.evaluate".equals(operation)) { + return "Script: " + getContext(opResult, "context"); + } else if ("com.evolveum.midpoint.model.impl.lens.AssignmentEvaluator.evaluateFromSegment".equals(operation)) { + String srcName = getContext(opResult, "segmentSourceName"); + String tgtName = getContext(opResult, "segmentTargetName"); + return "Segment: " + (srcName != null ? srcName + " " : "") + "->" + (tgtName != null ? " " + tgtName : ""); + } else if ("com.evolveum.midpoint.model.impl.lens.AssignmentEvaluator.evaluate".equals(operation)) { + String tgtName = getContext(opResult, "assignmentTargetName"); + return "AssignmentEvaluator.evaluate" + (tgtName != null ? " (-> " + tgtName + ")" : ""); + } else if ("com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentTripleEvaluator.evaluateAssignment".equals(operation)) { + String tgtName = getContext(opResult, "assignmentTargetName"); + return "AssignmentTripleEvaluator.evaluateAssignment" + (tgtName != null ? " (-> " + tgtName + ")" : ""); + } else if ("com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleProcessor.evaluateRule".equals(operation)) { + String triggeredString = getReturn(opResult, "triggered"); + int enabledActions = TraceUtil.getReturnsAsStringList(opResult, "enabledActions").size(); + String triggeredSuffix = "true".equals(triggeredString) ? " # (" + enabledActions + ")" : ""; + return "Evaluate rule: " + getParameter(opResult, "policyRule") + triggeredSuffix; + } + + switch (this) { + case CLOCKWORK_RUN: return "Clockwork run"; + case CLOCKWORK_CLICK: return "Clockwork click"; + case PROJECTOR_PROJECT: return "Projector"; + case PROJECTOR_COMPONENT: + if ("projection".equals(last)) { + return "Projector projection: " + getParameter(opResult, "resourceName"); + } else { + return "Projector " + last; + } + case CLOCKWORK_METHOD: return "Clockwork " + last; + + case MAPPING_EVALUATION: return "Mapping: " + getMappingInfo(opResult); + case MAPPING_PREPARATION: return "Mapping preparation"; + case MAPPING_EVALUATION_PREPARED: return "Prepared mapping evaluation"; + + // TODO script + case CHANGE_EXECUTION: return "Change execution"; + case FOCUS_CHANGE_EXECUTION: return "Change execution for focus (" + last + ")"; + case PROJECTION_CHANGE_EXECUTION: return "Change execution for focus (" + last + ")"; + case CHANGE_EXECUTION_DELTA: return "Delta execution"; + case CHANGE_EXECUTION_OTHER: return "Change execution - " + last; + case REPOSITORY: return "Repository " + last + commaQualifiers; + case REPOSITORY_CACHE: + return getRepoCacheOpDescription(node, opResult, last, commaQualifiers); + case PROVISIONING_API: return "Provisioning " + last + commaQualifiers; + case PROVISIONING_INTERNAL: return getLastTwo(operation) + commaQualifiers; + case FOCUS_LOAD: return "Focus load"; + case FOCUS_LOAD_CHECK: return "Focus load check (" + node.getResultComment() + ")"; + case SHADOW_LOAD: return "Shadow load"; + case MODEL_OTHER: + case OTHER: + return getLastTwo(operation) + commaQualifiers; + } + return opResult.getOperation() + (qualifiers.isEmpty() ? "" : " (" + qualifiers + ")"); + } + + private String getRepoCacheOpDescription(OpNode node, OperationResultType opResult, String last, String commaQualifiers) { + String postfix = ""; + if ("getObject".equals(last)) { + postfix = " - " + getContext(opResult, "objectName"); + } else if ("searchObjects".equals(last) || "searchObjectsIterative".equals(last)) { + String objectsFound = getReturn(opResult, "objectsFound"); + if (objectsFound != null) { + postfix = " - " + objectsFound + " obj(s)"; + } + RepositorySearchObjectsTraceType trace = getTrace(opResult, RepositorySearchObjectsTraceType.class); + if (trace != null && trace.getObjectRef().size() == 1) { + String name = getName(trace.getObjectRef().get(0)); + if (name != null) { + postfix += " - " + name; + } + } + } else if ("addObject".equals(last)) { + RepositoryAddTraceType add = getTrace(opResult, RepositoryAddTraceType.class); + String name = add != null ? getName(add.getObjectRef()) : null; + if (name != null) { + postfix = " - " + name; + } + } else if ("modifyObject".equals(last)) { + RepositoryModifyTraceType trace = getTrace(opResult, RepositoryModifyTraceType.class); + if (trace != null) { + postfix += " - " + trace.getModification().size() + " mod(s)"; + PrismObject object = node.getTraceInfo().findObject(trace.getOid()); + if (object != null) { + postfix += " - " + PolyString.getOrig(object.getName()); + } + } + } + return "Cache " + last + commaQualifiers + postfix; + } + + private String getName(ObjectReferenceType ref) { + if (ref != null && ref.getObject() != null) { + return PolyString.getOrig(ref.getObject().getName()); + } else { + return null; + } + } + + private String getMappingInfo(OperationResultType result) { + MappingEvaluationTraceType trace = getTrace(result, MappingEvaluationTraceType.class); + String context = getContext(result, "context"); + if (trace != null) { + MappingType mapping = trace.getMapping(); + if (mapping != null) { + if (mapping.getName() != null) { + return mapping.getName(); + } else { + String sources = mapping.getSource().stream() + .map(source -> stringifyPath(source.getPath())) + .collect(Collectors.joining(", ")); + if (mapping.getTarget() != null && mapping.getTarget().getPath() != null) { + String sourcesPlusSpace = sources.isEmpty() ? "" : sources + " "; + return sourcesPlusSpace + "-> " + stringifyPath(mapping.getTarget().getPath()); + } else { + return context + (sources.isEmpty() ? "" : " <- " + sources); + } + } + } + } + return context; + } + + private String stringifyPath(ItemPathType pathBean) { + if (pathBean != null) { + ItemPath path = pathBean.getItemPath(); + return path.stripVariableSegment().toString(); + } else { + return "(no path)"; + } + } + + private String getLastTwo(String operation) { + int i = StringUtils.lastOrdinalIndexOf(operation, ".", 2); + if (i < 0) { + return operation; + } else { + return operation.substring(i+1); + } + } + + private String getLast(String operation) { + return StringUtils.substringAfterLast(operation, "."); + } + + private String toRegex(String pattern) { + return pattern.replace(".", "\\.").replace("*", ".*"); + } + + public static OpType determine(OperationResultType operation) { + for (OpType type : OpType.values()) { + if (type.matches(operation)) { + return type; + } + } + return null; + } + + private boolean matches(OperationResultType operation) { + for (Pattern pattern : compiledPatterns) { + if (pattern.matcher(operation.getOperation()).matches()) { + if (predicate == null || predicate.apply(operation)) { + return true; + } + } + } + return false; + } + + public String getLabel() { + return label; + } + + public OperationKindType getKind() { + return kind; + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OperationCategorizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OperationCategorizer.java new file mode 100644 index 00000000000..89a97b83fac --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/OperationCategorizer.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingOutputType; + +import org.jetbrains.annotations.NotNull; + +/** + * Categorizes operations in tracing output. + */ +@Experimental +class OperationCategorizer { + + @NotNull private final TracingOutputType tracingOutput; + + OperationCategorizer(@NotNull TracingOutputType tracingOutput) { + this.tracingOutput = tracingOutput; + } + + void categorize() { + if (tracingOutput.getResult() != null) { + categorize(tracingOutput.getResult()); + } + } + + private void categorize(OperationResultType result) { + if (result.getOperationKind() == null) { + result.setOperationKind(determineOperationKind(result)); + } + result.getPartialResults().forEach(this::categorize); + } + + private OperationKindType determineOperationKind(OperationResultType result) { + OpType type = OpType.determine(result); + return type != null ? type.getKind() : null; + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/Options.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/Options.java new file mode 100644 index 00000000000..d915bd22204 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/Options.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationKindType; + +import java.util.HashSet; +import java.util.Set; + +@Experimental +public class Options { + private final Set kindsToShow = new HashSet<>(); + private final Set typesToShow = new HashSet<>(); + private final Set categoriesToShow = new HashSet<>(); + private boolean showAlsoParents; + private boolean showPerformanceColumns; + private boolean showReadWriteColumns; + + public boolean isShowAlsoParents() { + return showAlsoParents; + } + public void setShowAlsoParents(boolean showAlsoParents) { + this.showAlsoParents = showAlsoParents; + } + public Set getTypesToShow() { + return typesToShow; + } + + public Set getKindsToShow() { + return kindsToShow; + } + + public Set getCategoriesToShow() { + return categoriesToShow; + } + public boolean isShowPerformanceColumns() { + return showPerformanceColumns; + } + public void setShowPerformanceColumns(boolean showPerformanceColumns) { + this.showPerformanceColumns = showPerformanceColumns; + } + public boolean isShowReadWriteColumns() { + return showReadWriteColumns; + } + public void setShowReadWriteColumns(boolean showReadWriteColumns) { + this.showReadWriteColumns = showReadWriteColumns; + } + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/PerformanceCategory.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/PerformanceCategory.java new file mode 100644 index 00000000000..05005a39692 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/PerformanceCategory.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2020 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.traces; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; + +@Experimental +public enum PerformanceCategory { + REPOSITORY("Repo", "Repository (all)", + "com.evolveum.midpoint.repo.api.*", + "com.evolveum.midpoint.repo.impl.*"), + REPOSITORY_READ("Repo:R", "Repository (read)", + "com.evolveum.midpoint.repo.api.RepositoryService.getObject", + "com.evolveum.midpoint.repo.api.RepositoryService.getVersion", + "com.evolveum.midpoint.repo.api.RepositoryService.searchObjects", + "com.evolveum.midpoint.repo.api.RepositoryService.searchObjectsIterative", + "com.evolveum.midpoint.repo.api.RepositoryService.listAccountShadowOwner", + "com.evolveum.midpoint.repo.api.RepositoryService.searchContainers", + "com.evolveum.midpoint.repo.api.RepositoryService.countContainers", + "com.evolveum.midpoint.repo.api.RepositoryService.listResourceObjectShadows", + "com.evolveum.midpoint.repo.api.RepositoryService.countObjects", + "com.evolveum.midpoint.repo.api.RepositoryService.searchShadowOwner" + // TODO impl + ), + REPOSITORY_WRITE("Repo:W", "Repository (write)", + "com.evolveum.midpoint.repo.api.RepositoryService.addObject", + "com.evolveum.midpoint.repo.api.RepositoryService.modifyObject", + "com.evolveum.midpoint.repo.api.RepositoryService.deleteObject", + "com.evolveum.midpoint.repo.api.RepositoryService.advanceSequence", + "com.evolveum.midpoint.repo.api.RepositoryService.returnUnusedValuesToSequence", + "com.evolveum.midpoint.repo.api.RepositoryService.addDiagnosticInformation"), + REPOSITORY_OTHER("Repo:O", "Repository (other)", Arrays.asList(REPOSITORY), Arrays.asList(REPOSITORY_READ, REPOSITORY_WRITE)), + REPOSITORY_CACHE("RCache", "Repository cache (all)", + "com.evolveum.midpoint.repo.cache.RepositoryCache.*"), + REPOSITORY_CACHE_READ("RCache:R", "Repository cache (read)", + "com.evolveum.midpoint.repo.cache.RepositoryCache.getObject", + "com.evolveum.midpoint.repo.cache.RepositoryCache.getVersion", + "com.evolveum.midpoint.repo.cache.RepositoryCache.searchObjects", + "com.evolveum.midpoint.repo.cache.RepositoryCache.searchObjectsIterative", + "com.evolveum.midpoint.repo.cache.RepositoryCache.listAccountShadowOwner", + "com.evolveum.midpoint.repo.cache.RepositoryCache.searchContainers", + "com.evolveum.midpoint.repo.cache.RepositoryCache.countContainers", + "com.evolveum.midpoint.repo.cache.RepositoryCache.listResourceObjectShadows", + "com.evolveum.midpoint.repo.cache.RepositoryCache.countObjects", + "com.evolveum.midpoint.repo.cache.RepositoryCache.searchShadowOwner"), + REPOSITORY_CACHE_WRITE("RCache:R", "Repository cache (write)", + "com.evolveum.midpoint.repo.cache.RepositoryCache.addObject", + "com.evolveum.midpoint.repo.cache.RepositoryCache.modifyObject", + "com.evolveum.midpoint.repo.cache.RepositoryCache.deleteObject", + "com.evolveum.midpoint.repo.cache.RepositoryCache.advanceSequence", + "com.evolveum.midpoint.repo.cache.RepositoryCache.returnUnusedValuesToSequence", + "com.evolveum.midpoint.repo.cache.RepositoryCache.addDiagnosticInformation"), + REPOSITORY_CACHE_OTHER("RCache:O", "Repository cache (other)", Arrays.asList(REPOSITORY_CACHE), Arrays.asList(REPOSITORY_CACHE_READ, REPOSITORY_CACHE_WRITE)), + MAPPING_EVALUATION("Map", "Mapping evaluation", "com.evolveum.midpoint.model.common.mapping.MappingImpl.evaluate"), + ICF("ConnId", "ConnId (all)", "org.identityconnectors.framework.api.ConnectorFacade.*"), + ICF_READ("ConnId:R", "ConnId (read)", + "org.identityconnectors.framework.api.ConnectorFacade.getObject", + "org.identityconnectors.framework.api.ConnectorFacade.sync", + "org.identityconnectors.framework.api.ConnectorFacade.search"), + ICF_WRITE("ConnId:W", "ConnId (write)", + "org.identityconnectors.framework.api.ConnectorFacade.create", + "org.identityconnectors.framework.api.ConnectorFacade.updateDelta", + "org.identityconnectors.framework.api.ConnectorFacade.addAttributeValues", + "org.identityconnectors.framework.api.ConnectorFacade.update", + "org.identityconnectors.framework.api.ConnectorFacade.removeAttributeValues", + "org.identityconnectors.framework.api.ConnectorFacade.delete"), + ICF_SCHEMA("ConnId:S", "ConnId (schema)", + "org.identityconnectors.framework.api.ConnectorFacade.getSupportedOperations", + "org.identityconnectors.framework.api.ConnectorFacade.schema"), + ICF_OTHER("ConnId:O", "ConnId (other)", Arrays.asList(ICF), Arrays.asList(ICF_READ, ICF_WRITE, ICF_SCHEMA)); + + private final String shortLabel; + private final String label; + private final List plus, minus; + private final List patterns; + private final List compiledPatterns; + + PerformanceCategory(String shortLabel, String label, List plus, List minus) { + this.shortLabel = shortLabel; + this.label = label; + this.plus = plus; + this.minus = minus; + this.patterns = null; + this.compiledPatterns = null; + } + + PerformanceCategory(String shortLabel, String label, String... patterns) { + this.shortLabel = shortLabel; + this.label = label; + this.plus = this.minus = null; + this.patterns = Arrays.asList(patterns); + this.compiledPatterns = new ArrayList<>(); + for (String pattern : patterns) { + String regex = toRegex(pattern); + compiledPatterns.add(Pattern.compile(regex)); + } + } + + public String getShortLabel() { + return shortLabel; + } + + public String getLabel() { + return label; + } + + public boolean isDerived() { + return plus != null || minus != null; + } + + public List getPlus() { + return plus; + } + + public List getMinus() { + return minus; + } + + private String toRegex(String pattern) { + return pattern.replace(".", "\\.").replace("*", ".*"); + } + + public boolean matches(OperationResultType operation) { + for (Pattern pattern : compiledPatterns) { + if (pattern.matcher(operation.getOperation()).matches()) { + return true; + } + } + return false; + } + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/PerformanceCategoryInfo.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/PerformanceCategoryInfo.java new file mode 100644 index 00000000000..e6ef408c358 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/PerformanceCategoryInfo.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.util.annotation.Experimental; + +@Experimental +public class PerformanceCategoryInfo { + private long ownTime; + private long totalTime; + private int ownCount; + private int totalCount; + public long getOwnTime() { + return ownTime; + } + public void setOwnTime(long ownTime) { + this.ownTime = ownTime; + } + public long getTotalTime() { + return totalTime; + } + public void setTotalTime(long totalTime) { + this.totalTime = totalTime; + } + public int getOwnCount() { + return ownCount; + } + public void setOwnCount(int ownCount) { + this.ownCount = ownCount; + } + public int getTotalCount() { + return totalCount; + } + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceInfo.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceInfo.java new file mode 100644 index 00000000000..ae86385e827 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceInfo.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TraceDictionaryEntryType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingOutputType; + +@Experimental +public class TraceInfo { + private final TracingOutputType tracingOutput; + + public TraceInfo(TracingOutputType tracingOutput) { + this.tracingOutput = tracingOutput; + } + + public TracingOutputType getTracingOutput() { + return tracingOutput; + } + + public PrismObject findObject(String oid) { + if (oid == null || tracingOutput == null || tracingOutput.getDictionary() == null) { + return null; + } + for (TraceDictionaryEntryType entry : tracingOutput.getDictionary().getEntry()) { + if (oid.equals(entry.getObject().getOid())) { + PrismObject object = entry.getObject().getObject(); + if (object != null) { + return object; + } + } + } + return null; + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceParser.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceParser.java new file mode 100644 index 00000000000..7176ec156f9 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceParser.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 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.traces; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingOutputType; + +@Experimental +public class TraceParser { + + private static final Trace LOGGER = TraceManager.getTrace(TraceParser.class); + + @NotNull private final PrismContext prismContext; + + @SuppressWarnings("WeakerAccess") // used externally + public TraceParser(@NotNull PrismContext prismContext) { + this.prismContext = prismContext; + } + + public TracingOutputType parse(File file) throws IOException, SchemaException { + boolean isZip = file.getName().toLowerCase().endsWith(".zip"); + return parse(new FileInputStream(file), isZip, file.getPath()); + } + + public TracingOutputType parse(InputStream inputStream, boolean isZip, String description) throws SchemaException, IOException { + TracingOutputType wholeTracingOutput = getObject(inputStream, isZip, description); + if (wholeTracingOutput != null) { + new DictionaryExpander(wholeTracingOutput).expand(); + new OperationCategorizer(wholeTracingOutput).categorize(); + } + return wholeTracingOutput; + } + + public TracingOutputType getObject(InputStream stream, boolean isZip, String description) throws IOException, SchemaException { + long start = System.currentTimeMillis(); + Object object; + if (isZip) { + try (ZipInputStream zis = new ZipInputStream(stream)) { + ZipEntry zipEntry = zis.getNextEntry(); + if (zipEntry != null) { + object = prismContext.parserFor(zis).xml().compat().parseRealValue(); + } else { + LOGGER.error("No zip entry in input file '{}'", description); + object = null; + } + } + } else { + object = prismContext.parserFor(stream).xml().compat().parseRealValue(); + } + stream.close(); + long read = System.currentTimeMillis(); + LOGGER.info("Read the content of {} in {} milliseconds", description, read - start); + + if (object instanceof TracingOutputType) { + return (TracingOutputType) object; + } else if (object instanceof OperationResultType) { + TracingOutputType rv = new TracingOutputType(prismContext); + rv.setResult((OperationResultType) object); + return rv; + } else { + LOGGER.error("Wrong object type in input file '{}': {}", description, object); + return null; + } + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceUtil.java new file mode 100644 index 00000000000..e614bbfcb82 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/TraceUtil.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020 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.traces; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.JAXBElement; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.EntryType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ParamsType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TraceType; +import com.evolveum.prism.xml.ns._public.types_3.RawType; + +@Experimental +public class TraceUtil { + + @SuppressWarnings("unchecked") + public static T getTrace(OperationResultType result, Class aClass) { + for (TraceType trace : result.getTrace()) { + if (aClass.isAssignableFrom(trace.getClass())) { + return (T) trace; + } + } + return null; + } + + public static String getContext(OperationResultType opResult, String name) { + if (opResult.getContext() != null) { + for (EntryType e : opResult.getContext().getEntry()) { + if (name.equals(e.getKey())) { + return dump(e.getEntryValue()); + } + } + } + return ""; + } + + public static String getParameter(OperationResultType opResult, String name) { + if (opResult.getParams() != null) { + for (EntryType e : opResult.getParams().getEntry()) { + if (name.equals(e.getKey())) { + return dump(e.getEntryValue()); + } + } + } + return ""; + } + + public static List> selectByKey(ParamsType params, String key) { + if (params != null) { + return params.getEntry().stream() + .filter(e -> key.equals(e.getKey())) + .map(e -> e.getEntryValue()) + .collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + + public static String getReturn(OperationResultType opResult, String name) { + return String.join(", ", getReturnsAsStringList(opResult, name).toArray(new String[0])); + } + + public static List getReturnsAsStringList(OperationResultType opResult, String name) { + return asStringList(selectByKey(opResult.getReturns(), name)); + } + + private static List asStringList(List> elements) { + return elements.stream() + .map(e -> dump(e)) + .collect(Collectors.toList()); + } + + public static String dump(JAXBElement jaxb) { + if (jaxb == null) { + return ""; + } + Object value = jaxb.getValue(); + if (value instanceof RawType) { + return ((RawType) value).extractString(); + } else { + return String.valueOf(value); + } + } + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/ViewOptions.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/ViewOptions.java new file mode 100644 index 00000000000..c39ed76fe62 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/ViewOptions.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import java.util.HashSet; +import java.util.Set; + +@Experimental +public class ViewOptions { + + private final Set showOperationTypes = new HashSet<>(); + + public Set getShowOperationTypes() { + return showOperationTypes; + } + + public void show(OpType... types) { + for (OpType type : types) { + showOperationTypes.add(type); + } + } + + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/ViewedObject.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/ViewedObject.java new file mode 100644 index 00000000000..dd7bc7c1b84 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/ViewedObject.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 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.traces; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +@Experimental +public class ViewedObject { + private String label; + private PrismObject object; + + public ViewedObject(String label, PrismObject object) { + this.label = label; + this.object = object; + } + public String getLabel() { + return label; + } + public void setLabel(String label) { + this.label = label; + } + public PrismObject getObject() { + return object; + } + public void setObject(PrismObject object) { + this.object = object; + } + + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/BaseVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/BaseVisualizer.java new file mode 100644 index 00000000000..6e30a7b27e1 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/BaseVisualizer.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.traces.OpNode; + +import com.evolveum.midpoint.schema.traces.PerformanceCategory; +import com.evolveum.midpoint.schema.traces.PerformanceCategoryInfo; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GenericTraceVisualizationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.prism.xml.ns._public.types_3.*; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.xml.bind.JAXBElement; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static com.evolveum.midpoint.util.QNameUtil.getLocalPart; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +@Experimental +abstract class BaseVisualizer implements Visualizer { + + private static final int MAX_CODE_CHARS = 120; + + private static final String SEPARATOR_FIRST = " | "; + private static final String SEPARATOR_CONTINUATION = " | "; + + @Autowired TraceVisualizerRegistry registry; + + PrismContext prismContext; + + BaseVisualizer(PrismContext prismContext) { + this.prismContext = prismContext; + } + + void defaultOneLineVisualization(StringBuilder sb, OpNode node, int indent) { + indent(sb, node, indent); + sb.append(node.getOperationNameFormatted()); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + } + + void appendInvocationIdAndDuration(StringBuilder sb, OpNode node) { + if (node.isShowInvocationId()) { + sb.append(" #").append(node.getInvocationId()); + } + if (node.isShowDurationAfter()) { + sb.append(" - ").append(node.getMillisecondsFormatted()).append(" ms"); + } + } + + String indent(StringBuilder sb, OpNode opNode, int indent) { + String nullPrefix = printPerfData(sb, opNode); + String indentText = StringUtils.repeat(" ", indent); + sb.append(indentText); + return nullPrefix + indentText; + } + + private String printPerfData(StringBuilder sb, OpNode opNode) { + StringBuilder nullPrefix = new StringBuilder(); + boolean showTotals = opNode.showTotals(); + char showTotalsFlag = showTotals ? '*' : ' '; + if (opNode.isShowDurationBefore()) { + Double milliseconds = opNode.getMilliseconds(); + String text; + if (milliseconds != null) { + text = String.format("%8.1f ms", defaultIfNull(milliseconds, 0.0)); + } else { + text = StringUtils.repeat(" ", 11); + } + appendTextOrSpaces(sb, text, true); + sb.append(SEPARATOR_FIRST); + appendTextOrSpaces(nullPrefix, text, false); + nullPrefix.append(SEPARATOR_CONTINUATION); + } + for (Pair pair : opNode.getCounts()) { + PerformanceCategory category = pair.getLeft(); + PerformanceCategoryInfo info = pair.getRight(); + int count = showTotals ? info.getTotalCount() : info.getOwnCount(); + String text = String.format("%s %5d%c", category.getShortLabel(), count, showTotalsFlag); + appendTextOrSpaces(sb, text, count > 0); + sb.append(SEPARATOR_FIRST); + appendTextOrSpaces(nullPrefix, text, false); + nullPrefix.append(SEPARATOR_CONTINUATION); + } + for (Pair pair : opNode.getTimes()) { + PerformanceCategory category = pair.getLeft(); + PerformanceCategoryInfo info = pair.getRight(); + long time = showTotals ? info.getTotalTime() : info.getOwnTime(); + String text = String.format("%s %8.1f ms%c", category.getShortLabel(), time / 1000.0, showTotalsFlag); + appendTextOrSpaces(sb, text, time > 0); + sb.append(SEPARATOR_FIRST); + appendTextOrSpaces(nullPrefix, text, false); + nullPrefix.append(SEPARATOR_CONTINUATION); + } + return nullPrefix.toString(); + } + + private void appendTextOrSpaces(StringBuilder sb, String text, boolean condition) { + if (condition) { + sb.append(text); + } else { + sb.append(spaces(text)); + } + } + + private String spaces(String text) { + return StringUtils.repeat(' ', text.length()); + } + + void appendWithPrefix(StringBuilder sb, String prefix, String text) { + for (String line : text.split("\\n")) { + sb.append(prefix).append(line).append("\n"); + } + } + + String formatMultiLine(ItemDeltaItemType idi, int indent) { + return "NYI"; + } + + String formatSingleLine(ItemDeltaItemType idi, GenericTraceVisualizationType generic) { + if (idi.getDelta().isEmpty() && java.util.Objects.equals(idi.getOldItem(), idi.getNewItem())) { + return formatSingleLine(idi.getOldItem(), false, generic); + } else { + if (generic == GenericTraceVisualizationType.BRIEF) { + return formatSingleLine(idi.getOldItem(), false, generic) + " -> " + formatSingleLine(idi.getNewItem(), false, generic); + } else { + return formatSingleLine(idi.getOldItem(), false, generic) + " + " + formatSingleLine(idi.getDelta()) + " = " + formatSingleLine(idi.getNewItem(), false, generic); + } + } + } + + @SuppressWarnings("SameParameterValue") + private String formatSingleLine(ItemType item, boolean showName, GenericTraceVisualizationType generic) { + if (item != null) { + StringBuilder sb = new StringBuilder(); + if (showName) { + sb.append(getLocalPart(item.getName())).append(": "); + } + formatSingleLine(sb, item.getValue()); + return sb.toString(); + } else { + return "(no values)"; + } + } + + private void formatSingleLine(StringBuilder sb, List values) { + if (values.isEmpty()) { + sb.append("(no values)"); + } else if (values.size() == 1) { + sb.append(formatSingleLine(values.get(0))); + } else { + sb.append(values.stream() + .map(this::formatSingleLine) + .collect(Collectors.joining(", ", "(", ")"))); + } + } + + private String formatSingleLine(Object value) { + if (value instanceof RawType) { + try { + return ((RawType) value).guessFormattedValue(); + } catch (SchemaException e) { + return String.valueOf(value); + } + } else { + return String.valueOf(value); + } + } + + boolean isSingleLine(ItemDeltaItemType idi, GenericTraceVisualizationType generic) { + return true;// || formatSingleLine(idi).length() < 80; + } + + String formatSingleLine(DeltaSetTripleType deltaSetTriple, GenericTraceVisualizationType generic) { + if (deltaSetTriple != null) { + StringBuilder sb = new StringBuilder(); + if (!deltaSetTriple.getZero().isEmpty()) { + formatSingleLine(sb, deltaSetTriple.getZero()); + } + if (!deltaSetTriple.getMinus().isEmpty() || !deltaSetTriple.getPlus().isEmpty()) { + if (sb.length() > 0) { + sb.append("; "); + } + if (deltaSetTriple.getMinus().isEmpty()) { + sb.append("Add: "); + formatSingleLine(sb, deltaSetTriple.getPlus()); + } else if (deltaSetTriple.getPlus().isEmpty()) { + sb.append("Remove: "); + formatSingleLine(sb, deltaSetTriple.getMinus()); + } else { + formatSingleLine(sb, deltaSetTriple.getMinus()); + sb.append(" -> "); + formatSingleLine(sb, deltaSetTriple.getPlus()); + } + } + if (sb.length() != 0) { + return sb.toString(); + } else { + return "(none)"; + } + } else { + return "(none)"; + } + } + + String shortEvaluatorDebugDump(JAXBElement evaluator, GenericTraceVisualizationType level) { + Object value = evaluator.getValue(); + if (value instanceof ScriptExpressionEvaluatorType) { + String code = ((ScriptExpressionEvaluatorType) value).getCode(); + if (code != null) { + if (level == GenericTraceVisualizationType.BRIEF || level == GenericTraceVisualizationType.DETAILED) { + return DebugUtil.excerpt(code.replaceAll("[\\s\\r\\n]+", " ").trim(), MAX_CODE_CHARS); + } else { + if (code.trim().contains("\n")) { + return code.trim(); + } else { + return code; + } + } + } else { + return "(script with no code)"; + } + } else { + return evaluator.getName().getLocalPart(); + } + } + + PrismObject removeOperationalItemsIfNeeded(PrismObject focus, GenericTraceVisualizationType generic) { + PrismObject objectToDisplay; + if (generic != GenericTraceVisualizationType.BRIEF) { + objectToDisplay = focus; + } else { + objectToDisplay = focus.clone(); + objectToDisplay.getValue().removeOperationalItems(); + } + return objectToDisplay; + } + + PrismObject removeShadowAuxiliaryItemsIfNeeded(PrismObject shadow, GenericTraceVisualizationType generic) { + PrismObject objectToDisplay; + if (generic != GenericTraceVisualizationType.BRIEF) { + objectToDisplay = shadow; + } else { + objectToDisplay = shadow.clone(); + objectToDisplay.getValue().removeOperationalItems(); + try { + objectToDisplay.getValue().keepPaths( + Arrays.asList( + ShadowType.F_NAME, + ShadowType.F_RESOURCE_REF, + ShadowType.F_SYNCHRONIZATION_SITUATION, + ShadowType.F_OBJECT_CLASS, + ShadowType.F_KIND, + ShadowType.F_INTENT, + ShadowType.F_ATTRIBUTES, + ShadowType.F_ASSOCIATION, + ShadowType.F_ACTIVATION, + ShadowType.F_CREDENTIALS + )); + } catch (SchemaException e) { + e.printStackTrace(); // todo logger + } + } + return objectToDisplay; + } + + // todo + String dumpDelta(ObjectDeltaType objectDelta, GenericTraceVisualizationType generic) { + try { + ObjectDelta delta = DeltaConvertor.createObjectDelta(objectDelta, prismContext); + ObjectDelta deltaToDisplay; + if (generic != GenericTraceVisualizationType.BRIEF) { + deltaToDisplay = delta; + } else { + deltaToDisplay = delta.clone(); + deltaToDisplay.removeOperationalItems(); + deltaToDisplay.removeEstimatedOldValues(); + } + return deltaToDisplay.debugDump(); + } catch (SchemaException e) { + return e.getMessage() + "\n" + ExceptionUtil.printStackTrace(e); + } + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ChangeExecutionVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ChangeExecutionVisualizer.java new file mode 100644 index 00000000000..1bb1e3b3721 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ChangeExecutionVisualizer.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import org.apache.commons.lang3.StringUtils; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +@Experimental +public class ChangeExecutionVisualizer extends BaseVisualizer { + + ChangeExecutionVisualizer(PrismContext prismContext) { + super(prismContext); + } + + @Override + public void visualize(StringBuilder sb, OpNode node, int indent) { + GenericTraceVisualizationType generic = defaultIfNull(node.getGenericVisualization(), GenericTraceVisualizationType.ONE_LINE); + + String label; + boolean isProjection; + if (node.getKind() == OperationKindType.FOCUS_CHANGE_EXECUTION) { + label = "Focus change execution"; + isProjection = false; + } else { + label = "Projection change execution"; + isProjection = true; + } + ModelExecuteDeltaTraceType trace = node.getTraceDownwards(ModelExecuteDeltaTraceType.class); + if (trace == null || trace.getDelta() == null || trace.getDelta().getObjectDeltaOperation() == null) { + indent(sb, node, indent); + sb.append(label); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + return; + } + + ObjectDeltaOperationType odo = trace.getDelta().getObjectDeltaOperation(); + String type = odo.getObjectDelta() != null ? String.valueOf(odo.getObjectDelta().getChangeType()) : "?"; + String nullPrefix = indent(sb, node, indent); + sb.append(label).append(": ").append(type).append(" of ").append(odo.getObjectName()); + if (isProjection) { + sb.append(" on ").append(odo.getResourceName()); + } + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + + if (generic != GenericTraceVisualizationType.ONE_LINE) { + String prefix = nullPrefix + " > "; + String prefixResult = nullPrefix + " = "; + appendWithPrefix(sb, prefix, dumpDelta(odo.getObjectDelta(), generic)); + appendWithPrefix(sb, prefixResult, showResultStatus(odo.getExecutionResult())); + } + } + + private String showResultStatus(OperationResultType result) { + return result != null ? String.valueOf(result.getStatus()) : null; + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ClockworkClickVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ClockworkClickVisualizer.java new file mode 100644 index 00000000000..c2f51175c13 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ClockworkClickVisualizer.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.util.annotation.Experimental; + +@Experimental +public class ClockworkClickVisualizer extends BaseVisualizer { + + ClockworkClickVisualizer(PrismContext prismContext) { + super(prismContext); + } + + @Override + public void visualize(StringBuilder sb, OpNode node, int indent) { + indent(sb, node, indent); + sb.append(node.getOperationNameFormatted()) + .append(": state ").append(node.getClockworkState()) + .append(", execution wave ").append(node.getExecutionWave()) + .append(", projection wave ").append(node.getProjectionWave()); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + } + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ClockworkExecutionVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ClockworkExecutionVisualizer.java new file mode 100644 index 00000000000..46c9f1264c4 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/ClockworkExecutionVisualizer.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.util.annotation.Experimental; + +@Experimental +public class ClockworkExecutionVisualizer extends BaseVisualizer { + + ClockworkExecutionVisualizer(PrismContext prismContext) { + super(prismContext); + } + + @Override + public void visualize(StringBuilder sb, OpNode node, int indent) { + indent(sb, node, indent); + sb.append(node.getOperationNameFormatted()) + .append(" with focus '") + .append(node.getFocusName()) + .append("'"); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/DefaultVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/DefaultVisualizer.java new file mode 100644 index 00000000000..98a39348be7 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/DefaultVisualizer.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.util.annotation.Experimental; + +@Experimental +public class DefaultVisualizer extends BaseVisualizer { + + DefaultVisualizer(PrismContext prismContext) { + super(prismContext); + } + + @Override + public void visualize(StringBuilder sb, OpNode node, int indent) { + defaultOneLineVisualization(sb, node, indent); + } + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/FocusLoadVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/FocusLoadVisualizer.java new file mode 100644 index 00000000000..27fcbb054e7 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/FocusLoadVisualizer.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusLoadedTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GenericTraceVisualizationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; + +@Experimental +public class FocusLoadVisualizer extends BaseVisualizer { + + FocusLoadVisualizer(PrismContext prismContext) { + super(prismContext); + } + + @Override + public void visualize(StringBuilder sb, OpNode node, int indent) { + GenericTraceVisualizationType generic = defaultIfNull(node.getGenericVisualization(), GenericTraceVisualizationType.ONE_LINE); + + String nullPrefix = indent(sb, node, indent); + sb.append("Focus loaded"); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + FocusLoadedTraceType trace = node.getTrace(FocusLoadedTraceType.class); + if (trace != null && generic != GenericTraceVisualizationType.ONE_LINE) { + ObjectReferenceType focusLoadedRef = trace.getFocusLoadedRef(); + PrismObject focus = focusLoadedRef != null ? focusLoadedRef.asReferenceValue().getObject() : null; + if (focus != null) { + String prefix = nullPrefix + " < "; + PrismObject objectToDisplay = removeOperationalItemsIfNeeded(focus, generic); + appendWithPrefix(sb, prefix, objectToDisplay.debugDump()); + } + } + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/FullProjectionLoadVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/FullProjectionLoadVisualizer.java new file mode 100644 index 00000000000..a15fe0e54b3 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/FullProjectionLoadVisualizer.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FullShadowLoadedTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GenericTraceVisualizationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; + +@Experimental +public class FullProjectionLoadVisualizer extends BaseVisualizer { + + FullProjectionLoadVisualizer(PrismContext prismContext) { + super(prismContext); + } + + @Override + public void visualize(StringBuilder sb, OpNode node, int indent) { + GenericTraceVisualizationType generic = defaultIfNull(node.getGenericVisualization(), GenericTraceVisualizationType.ONE_LINE); + + String nullPrefix = indent(sb, node, indent); + FullShadowLoadedTraceType trace = node.getTrace(FullShadowLoadedTraceType.class); + if (trace != null) { + ObjectReferenceType shadowLoadedRef = trace.getShadowLoadedRef(); + PrismObject shadow = shadowLoadedRef != null ? shadowLoadedRef.asReferenceValue().getObject() : null; + + if (shadow != null) { + sb.append("Projection (").append(shadow.asObjectable().getName()).append(") loaded from ").append(trace.getResourceName()); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + + if (generic != GenericTraceVisualizationType.ONE_LINE) { + String prefix = nullPrefix + " < "; + PrismObject objectToDisplay = removeShadowAuxiliaryItemsIfNeeded(shadow, generic); + appendWithPrefix(sb, prefix, objectToDisplay.debugDump()); + } + } else { + sb.append("Projection loaded from ").append(trace.getResourceName()); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + } + } else { + sb.append("Projection loaded"); + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + } + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/MappingEvaluationVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/MappingEvaluationVisualizer.java new file mode 100644 index 00000000000..354a214ced3 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/MappingEvaluationVisualizer.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +import static com.evolveum.midpoint.util.MiscUtil.emptyIfNull; +import static com.evolveum.midpoint.util.QNameUtil.getLocalPart; + +import java.util.List; +import java.util.stream.Collectors; +import javax.xml.bind.JAXBElement; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.DeltaSetTripleType; +import com.evolveum.prism.xml.ns._public.types_3.ItemDeltaItemType; + +public class MappingEvaluationVisualizer extends BaseVisualizer { + + MappingEvaluationVisualizer(PrismContext prismContext) { + super(prismContext); + } + + @Override + public void visualize(StringBuilder sb, OpNode node, int indent) { + GenericTraceVisualizationType generic = defaultIfNull(node.getGenericVisualization(), GenericTraceVisualizationType.ONE_LINE); + TraceDataSelectionType data = defaultIfNull(node.getDataSelection(), TraceDataSelectionType.ALL); // todo other default? + + boolean oneLine = generic == GenericTraceVisualizationType.ONE_LINE; + MappingEvaluationTraceType trace = node.getTrace(MappingEvaluationTraceType.class); + MappingKindType kind = trace != null ? trace.getMappingKind() : null; + MappingType mapping = trace != null ? trace.getMapping() : null; + String name = mapping != null ? mapping.getName() : null; + + String nullPrefix = indent(sb, node, indent); + if (trace != null) { + List sources = getSources(mapping, trace); + String target = getTarget(mapping, trace); + sb.append("Mapping - ").append(kind); + if (trace.getContainingObjectRef() != null && trace.getContainingObjectRef().getTargetName() != null) { + sb.append(" (").append(trace.getContainingObjectRef().getTargetName().getOrig()).append(")"); + } + sb.append(": ").append(emptyIfNull(name)); + if (!sources.isEmpty() || target != null) { + if (name != null) { + sb.append(" ("); + } + sb.append(String.join(", ", sources)); + if (!sources.isEmpty()) { + sb.append(" "); + } + sb.append("->"); + if (target != null) { + sb.append(" ").append(target); + } + if (name != null) { + sb.append(")"); + } + } + if (oneLine) { + if (trace.isConditionResultOld() != null || trace.isConditionResultNew() != null) { + if (!trace.isConditionResultOld() || !trace.isConditionResultNew()) { + sb.append(" [c: ").append(trace.isConditionResultOld()).append("->").append(trace.isConditionResultNew()).append("]"); + } + } + if (Boolean.FALSE.equals(trace.isTimeConstraintValid())) { + sb.append(" [time constraint not valid"); + if (trace.getNextRecomputeTime() != null) { + sb.append("; deferred to ").append(trace.getNextRecomputeTime()); + } + sb.append("]"); + } + } else { + // will be shown in detailed output + } + } else { + sb.append(node.getOperationNameFormatted()); // todo more context from op.result + } + appendInvocationIdAndDuration(sb, node); + sb.append("\n"); + + if (trace != null && generic != GenericTraceVisualizationType.ONE_LINE) { + String prefix = nullPrefix + " | "; + MappingStrengthType strength = getStrength(mapping); + if (generic != GenericTraceVisualizationType.BRIEF || strength != MappingStrengthType.NORMAL) { + sb.append(prefix).append("Strength: ").append(strength).append("\n"); + } + if (!isAuthoritative(mapping)) { + sb.append(prefix).append("Not authoritative\n"); + } + if (isExclusive(mapping)) { + sb.append(prefix).append("Exclusive\n"); + } + for (MappingSourceEvaluationTraceType source : trace.getSource()) { + appendWithPrefix(sb, prefix, shortSourceDump(source, generic)); + } + if (trace.isConditionResultOld() != null || trace.isConditionResultNew() != null) { + if (generic != GenericTraceVisualizationType.BRIEF || + !Boolean.TRUE.equals(trace.isConditionResultOld()) || + !Boolean.TRUE.equals(trace.isConditionResultNew())) { + appendWithPrefix(sb, prefix, "Condition: " + trace.isConditionResultOld() + " -> " + trace.isConditionResultNew()); + } + } + if (generic != GenericTraceVisualizationType.BRIEF || + !Boolean.TRUE.equals(trace.isTimeConstraintValid()) || + trace.getNextRecomputeTime() != null) { + appendWithPrefix(sb, prefix, "Time validity: " + trace.isTimeConstraintValid() + + (trace.getNextRecomputeTime() != null ? "; next recompute time is " + trace.getNextRecomputeTime() : "")); + } + appendWithPrefix(sb, prefix, "Output: " + shortOutputDump(trace.getOutput(), generic)); + appendWithPrefix(sb, prefix, "Expression: " + shortExpressionDebugDump(mapping, generic)); + + if (generic == GenericTraceVisualizationType.FULL) { + appendWithPrefix(sb, prefix, ((MappingEvaluationTraceType) (node.getResult().getTrace().get(0))).getTextTrace()); + } + } + } + + private String shortSourceDump(MappingSourceEvaluationTraceType source, GenericTraceVisualizationType generic) { + StringBuilder sb = new StringBuilder("Input "); + sb.append(getLocalPart(source.getName())).append(": "); + ItemDeltaItemType idi = source.getItemDeltaItem(); + if (idi != null) { + if (isSingleLine(idi, generic)) { + sb.append(formatSingleLine(idi, generic)).append("\n"); + } else { + sb.append("\n").append(formatMultiLine(idi, 1)); + } + } else { + sb.append("(no value)\n"); + } + return sb.toString(); + } + + private String shortOutputDump(DeltaSetTripleType output, GenericTraceVisualizationType generic) { + return formatSingleLine(output, generic); + } + + private boolean isAuthoritative(MappingType mapping) { + if (mapping != null) { + return defaultIfNull(mapping.isAuthoritative(), true); + } else { + return true; + } + } + + private boolean isExclusive(MappingType mapping) { + if (mapping != null) { + return defaultIfNull(mapping.isExclusive(), false); + } else { + return false; + } + } + + private MappingStrengthType getStrength(MappingType mapping) { + if (mapping != null) { + return defaultIfNull(mapping.getStrength(), MappingStrengthType.NORMAL); + } else { + return MappingStrengthType.NORMAL; + } + } + + private String getTarget(MappingType mapping, MappingEvaluationTraceType trace) { + if (mapping != null && mapping.getTarget() != null) { + return String.valueOf(mapping.getTarget().getPath()); + } else if (trace.getImplicitTargetPath() != null) { + return trace.getImplicitTargetPath().toString(); + } else { + return null; + } + } + + private List getSources(MappingType mapping, MappingEvaluationTraceType trace) { + if (mapping == null || mapping.getSource().isEmpty()) { + if (trace.getImplicitSourcePath() != null) { + return singletonList(String.valueOf(trace.getImplicitSourcePath())); + } else { + return emptyList(); + } + } else { + return mapping.getSource().stream() + .map(src -> String.valueOf(src.getPath())) + .collect(Collectors.toList()); + } + } + + private String shortExpressionDebugDump(MappingType mapping, GenericTraceVisualizationType level) { + List> evaluators; + if (mapping != null && mapping.getExpression() != null) { + evaluators = mapping.getExpression().getExpressionEvaluator(); + } else { + evaluators = emptyList(); + } + if (evaluators.isEmpty()) { + return "(none)"; + } else if (evaluators.size() == 1) { + return shortEvaluatorDebugDump(evaluators.get(0), level); + } else { + return evaluators.stream() + .map(evaluator -> shortEvaluatorDebugDump(evaluator, level)) + .collect(Collectors.joining(", ", "[", "]")); + } + } + +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/TraceTreeVisualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/TraceTreeVisualizer.java new file mode 100644 index 00000000000..c46dda89045 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/TraceTreeVisualizer.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import java.util.List; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.schema.traces.OpNode; + +@Experimental +public class TraceTreeVisualizer { + + @NotNull private final TraceVisualizerRegistry registry; + @NotNull private final List rootNodeList; + + public TraceTreeVisualizer(@NotNull TraceVisualizerRegistry registry, @NotNull List rootNodeList) { + this.registry = registry; + this.rootNodeList = rootNodeList; + } + + public String visualize() { + StringBuilder sb = new StringBuilder(); + for (OpNode rootNode : rootNodeList) { + visualize(sb, rootNode, 0); + } + return sb.toString(); + } + + private void visualize(StringBuilder sb, OpNode node, int indent) { + int indentDelta; + if (node.isVisible()) { + getVisualizer(node).visualize(sb, node, indent); + indentDelta = 1; + } else { + indentDelta = 0; + } + node.getChildren().forEach(child -> visualize(sb, child, indent + indentDelta)); + if (node.isVisible()) { + getVisualizer(node).visualizeAfter(sb, node, indent); + } + } + + private Visualizer getVisualizer(OpNode node) { + return registry.getVisualizer(node.getResult().getOperationKind()); + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/TraceVisualizerRegistry.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/TraceVisualizerRegistry.java new file mode 100644 index 00000000000..e235ac1d29e --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/TraceVisualizerRegistry.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import java.util.HashMap; +import java.util.Map; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationKindType; + +/** + * TODO rework + */ +@Experimental +public class TraceVisualizerRegistry { + + private final DefaultVisualizer defaultVisualizer; + + private Map visualizers = new HashMap<>(); + + public TraceVisualizerRegistry(PrismContext prismContext) { + defaultVisualizer = new DefaultVisualizer(prismContext); + visualizers.put(OperationKindType.CLOCKWORK_EXECUTION, new ClockworkExecutionVisualizer(prismContext)); + visualizers.put(OperationKindType.CLOCKWORK_CLICK, new ClockworkClickVisualizer(prismContext)); + visualizers.put(OperationKindType.MAPPING_EVALUATION, new MappingEvaluationVisualizer(prismContext)); + visualizers.put(OperationKindType.FOCUS_CHANGE_EXECUTION, new ChangeExecutionVisualizer(prismContext)); + visualizers.put(OperationKindType.PROJECTION_CHANGE_EXECUTION, new ChangeExecutionVisualizer(prismContext)); + visualizers.put(OperationKindType.FOCUS_LOAD, new FocusLoadVisualizer(prismContext)); + visualizers.put(OperationKindType.FULL_PROJECTION_LOAD, new FullProjectionLoadVisualizer(prismContext)); + } + + Visualizer getVisualizer(OperationKindType operationKind) { + Visualizer visualizer = visualizers.get(operationKind); + if (visualizer != null) { + return visualizer; + } else { + return defaultVisualizer; + } + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/Visualizer.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/Visualizer.java new file mode 100644 index 00000000000..ec313df5ce4 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/traces/visualizer/Visualizer.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 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.traces.visualizer; + +import com.evolveum.midpoint.schema.traces.OpNode; +import com.evolveum.midpoint.util.annotation.Experimental; + +@Experimental +public interface Visualizer { + + void visualize(StringBuilder sb, OpNode node, int indent); + + default void visualizeAfter(StringBuilder sb, OpNode node, int indent) { + } +} diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd index aa7f756b13d..6e05bbd8261 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd @@ -2782,10 +2782,12 @@ + Specification of the data flow(s) to be visualized. + Currently not implemented. @@ -2818,6 +2820,30 @@ + + + + What columns to visualize? + Volatile - will be changed soon. + E.g. we want to specify exact order of columns. + + + true + true + 4.1 + + + + + + + + + + + + + diff --git a/infra/schema/src/test/java/com/evolveum/midpoint/schema/traces/TestParseTrace.java b/infra/schema/src/test/java/com/evolveum/midpoint/schema/traces/TestParseTrace.java new file mode 100644 index 00000000000..7a9945ebff8 --- /dev/null +++ b/infra/schema/src/test/java/com/evolveum/midpoint/schema/traces/TestParseTrace.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2020 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.traces; + +import static org.testng.AssertJUnit.assertEquals; + +import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.GenericTraceVisualizationType.*; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import org.testng.annotations.Test; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.schema.AbstractSchemaTest; +import com.evolveum.midpoint.schema.traces.visualizer.TraceTreeVisualizer; +import com.evolveum.midpoint.schema.traces.visualizer.TraceVisualizerRegistry; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * Experimental. Unfinished. + */ +@Experimental +public class TestParseTrace extends AbstractSchemaTest { + + private static final File TEST_DIR = new File("src/test/resources/traces"); + + private static final File TRACE_MODIFY_GIVEN_NAME = new File(TEST_DIR, "trace-modify-given-name.zip"); + private static final File TRACE_MODIFY_EMPLOYEE_TYPE = new File(TEST_DIR, "trace-modify-employee-type.zip"); + private static final File TRACE_MODIFY_EMPLOYEE_TYPE_BUCCANEER = new File(TEST_DIR, "trace-modify-employee-type-buccaneer.zip"); + private static final File TRACE_MODIFY_COST_CENTER = new File(TEST_DIR, "trace-modify-cost-center.zip"); + private static final File TRACE_MODIFY_TELEPHONE_NUMBER = new File(TEST_DIR, "trace-modify-telephone-number.zip"); + private static final File TRACE_ASSIGN_DUMMY = new File(TEST_DIR, "trace-assign-dummy.zip"); + private static final File TRACE_RECONCILE_USER = new File(TEST_DIR, "trace-reconcile-user.zip"); + + @Test + public void test100ParseModifyGivenName() throws SchemaException, IOException { + testParseTrace(TRACE_MODIFY_GIVEN_NAME); + } + + @Test + public void test110ParseModifyEmployeeType() throws SchemaException, IOException { + testParseTrace(TRACE_MODIFY_EMPLOYEE_TYPE); + } + + @Test + public void test120ParseModifyEmployeeTypeBuccaneer() throws SchemaException, IOException { + testParseTrace(TRACE_MODIFY_EMPLOYEE_TYPE_BUCCANEER); + } + + @Test + public void test130ParseModifyCostCenter() throws SchemaException, IOException { + testParseTrace(TRACE_MODIFY_COST_CENTER); + } + + @Test + public void test140ParseModifyTelephoneNumber() throws SchemaException, IOException { + testParseTrace(TRACE_MODIFY_TELEPHONE_NUMBER); + } + + @Test + public void test150ParseAssignDummy() throws SchemaException, IOException { + testParseTrace(TRACE_ASSIGN_DUMMY); + } + + @Test + public void test160ParseReconcileUser() throws SchemaException, IOException { + testParseTrace(TRACE_RECONCILE_USER); + } + + private void testParseTrace(File file) throws IOException, SchemaException { + given(); + PrismContext prismContext = getPrismContext(); + TraceParser parser = new TraceParser(prismContext); + + TraceVisualizerRegistry registry = new TraceVisualizerRegistry(prismContext); + + when(); + TracingOutputType parsed = parser.parse(file); + List opNodeList = new OpNodeTreeBuilder(prismContext).build(parsed); + TraceVisualizationInstructionsType overview = getInstructions(prismContext, ONE_LINE, false); + TraceVisualizationInstructionsType overviewWithDurationBefore = showDurationBefore(getInstructions(prismContext, ONE_LINE, false)); + TraceVisualizationInstructionsType overviewWithColumns = + showColumns(getInstructions(prismContext, ONE_LINE, false)); + TraceVisualizationInstructionsType all = getInstructions(prismContext, ONE_LINE, true); + TraceVisualizationInstructionsType allWithColumns = + showColumns(getInstructions(prismContext, ONE_LINE, true)); + TraceVisualizationInstructionsType brief = getInstructions(prismContext, BRIEF, false); + TraceVisualizationInstructionsType briefWithColumns = showColumns(getInstructions(prismContext, BRIEF, false)); + TraceVisualizationInstructionsType detailed = getInstructions(prismContext, DETAILED, false); + TraceVisualizationInstructionsType full = getInstructions(prismContext, FULL, true); + + then(); + + TraceTreeVisualizer visualizer = new TraceTreeVisualizer(registry, opNodeList); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(overview)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("OVERVIEW:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(overviewWithDurationBefore)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("OVERVIEW WITH DURATION BEFORE:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(overviewWithColumns)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("OVERVIEW WITH COLUMNS:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(all)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("ALL:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(allWithColumns)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("ALL WITH COLUMNS:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(brief)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("BRIEF:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(briefWithColumns)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("BRIEF WITH COLUMNS:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(detailed)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("DETAILED:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + + opNodeList.forEach(node -> node.applyVisualizationInstructions(full)); + assertEquals("Wrong # of root opNodes", 1, opNodeList.size()); + System.out.println("FULL:\n"); + System.out.println(visualizer.visualize()); + + System.out.println("-----------------------------------------------------------------------------------------------"); + } + + private TraceVisualizationInstructionsType showColumns(TraceVisualizationInstructionsType instructions) { + TraceVisualizationColumnsType columns = new TraceVisualizationColumnsType(getPrismContext()); + columns.setInvocationId(true); + columns.setDuration(true); + columns.getTimeFor().add(PerformanceCategory.REPOSITORY.name()); + columns.getTimeFor().add(PerformanceCategory.ICF.name()); + columns.getCountFor().add(PerformanceCategory.REPOSITORY_READ.name()); + columns.getCountFor().add(PerformanceCategory.REPOSITORY_WRITE.name()); + columns.getCountFor().add(PerformanceCategory.ICF_READ.name()); + columns.getCountFor().add(PerformanceCategory.ICF_WRITE.name()); + instructions.setColumns(columns); + return instructions; + } + + private TraceVisualizationInstructionsType showDurationBefore(TraceVisualizationInstructionsType instructions) { + TraceVisualizationColumnsType columns = new TraceVisualizationColumnsType(getPrismContext()); + columns.setDurationBefore(true); + instructions.setColumns(columns); + return instructions; + } + + private TraceVisualizationInstructionsType getInstructions(PrismContext prismContext, GenericTraceVisualizationType level, + boolean other) { + TraceVisualizationInstructionsType instructions = new TraceVisualizationInstructionsType(prismContext) + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.CLOCKWORK_EXECUTION) + .operationKind(OperationKindType.CLOCKWORK_CLICK) + .end() + .beginVisualization() + .generic(GenericTraceVisualizationType.ONE_LINE) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.MAPPING_EVALUATION) + .end() + .beginVisualization() + .generic(level) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.FOCUS_LOAD) + .end() + .beginVisualization() + .generic(level) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.FULL_PROJECTION_LOAD) + .end() + .beginVisualization() + .generic(level) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.FOCUS_CHANGE_EXECUTION) + .end() + .beginVisualization() + .generic(level) + .end() + .end() + .beginInstruction() + .beginSelector() + .operationKind(OperationKindType.PROJECTION_CHANGE_EXECUTION) + .end() + .beginVisualization() + .generic(level) + .end() + .end(); + if (other) { + instructions.getInstruction().add( + new TraceVisualizationInstructionType(prismContext) + .beginVisualization() + .generic(ONE_LINE) + .end()); + } + return instructions; + } +} diff --git a/infra/schema/src/test/resources/traces/trace-assign-dummy.zip b/infra/schema/src/test/resources/traces/trace-assign-dummy.zip new file mode 100644 index 00000000000..594467fda62 Binary files /dev/null and b/infra/schema/src/test/resources/traces/trace-assign-dummy.zip differ diff --git a/infra/schema/src/test/resources/traces/trace-modify-cost-center.zip b/infra/schema/src/test/resources/traces/trace-modify-cost-center.zip new file mode 100644 index 00000000000..ec8acd30dce Binary files /dev/null and b/infra/schema/src/test/resources/traces/trace-modify-cost-center.zip differ diff --git a/infra/schema/src/test/resources/traces/trace-modify-employee-type-buccaneer.zip b/infra/schema/src/test/resources/traces/trace-modify-employee-type-buccaneer.zip new file mode 100644 index 00000000000..5c62b24a2ca Binary files /dev/null and b/infra/schema/src/test/resources/traces/trace-modify-employee-type-buccaneer.zip differ diff --git a/infra/schema/src/test/resources/traces/trace-modify-employee-type.zip b/infra/schema/src/test/resources/traces/trace-modify-employee-type.zip new file mode 100644 index 00000000000..a076d7dfb9f Binary files /dev/null and b/infra/schema/src/test/resources/traces/trace-modify-employee-type.zip differ diff --git a/infra/schema/src/test/resources/traces/trace-modify-given-name.zip b/infra/schema/src/test/resources/traces/trace-modify-given-name.zip new file mode 100644 index 00000000000..e568452011e Binary files /dev/null and b/infra/schema/src/test/resources/traces/trace-modify-given-name.zip differ diff --git a/infra/schema/src/test/resources/traces/trace-modify-telephone-number.zip b/infra/schema/src/test/resources/traces/trace-modify-telephone-number.zip new file mode 100644 index 00000000000..455f5db3214 Binary files /dev/null and b/infra/schema/src/test/resources/traces/trace-modify-telephone-number.zip differ diff --git a/infra/schema/src/test/resources/traces/trace-reconcile-user.zip b/infra/schema/src/test/resources/traces/trace-reconcile-user.zip new file mode 100644 index 00000000000..942ca8b29a1 Binary files /dev/null and b/infra/schema/src/test/resources/traces/trace-reconcile-user.zip differ diff --git a/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/AuthorizationConstants.java b/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/AuthorizationConstants.java index 3da9817aa80..e62d6f4193c 100644 --- a/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/AuthorizationConstants.java +++ b/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/AuthorizationConstants.java @@ -225,6 +225,9 @@ public class AuthorizationConstants { public static final QName AUTZ_UI_CONFIGURATION_EVALUATE_MAPPING = new QName(NS_AUTHORIZATION_UI, "configEvaluateMapping"); public static final String AUTZ_UI_CONFIGURATION_EVALUATE_MAPPING_URL = NS_AUTHORIZATION_UI + "#configEvaluateMapping"; + public static final QName AUTZ_UI_TRACE_VIEW = new QName(NS_AUTHORIZATION_UI, "traceView"); + public static final String AUTZ_UI_TRACE_VIEW_URL = NS_AUTHORIZATION_UI + "#traceView"; + //Roles public static final QName AUTZ_UI_ROLES_ALL_QNAME = new QName(NS_AUTHORIZATION_UI, "rolesAll"); public static final String AUTZ_UI_ROLES_ALL_URL = NS_AUTHORIZATION_UI + "#rolesAll";