diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt index 270c540549..17e34f920d 100644 --- a/Documentation/rest-api-config.txt +++ b/Documentation/rest-api-config.txt @@ -632,6 +632,65 @@ The entries in the map are sorted by capability ID. } ---- +[[list-tasks]] +=== List Tasks +-- +'GET /config/server/tasks/' +-- + +Lists the tasks from the background work queues that the Gerrit daemon +is currently performing, or will perform in the near future. + +Gerrit contains an internal scheduler, similar to cron, that it uses to +queue and dispatch both short and long term tasks. + +Tasks that are completed or canceled exit the queue very quickly once +they enter this state, but it can be possible to observe tasks in these +states. + +End-users may see a task only if they can also see the project the task +is associated with. Tasks operating on other projects, or that do not +have a specific project, are hidden. + +Members of a group that is granted the +link:access-control.html#capability_viewQueue[View Queue] capability or +the link:access-control.html#capability_administrateServer[Administrate +Server] capability can see all tasks. + +As result a list of link:#task-info[TaskInfo] entities is returned. + +The entries in the list are sorted by task state, remaining delay and +command. + +.Request +---- + GET /config/server/tasks/ HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + + )]}' + [ + { + "id": "1e688bea", + "state": "SLEEPING", + "start_time": "2014-06-11 12:58:51.991000000", + "delay": 3453, + "command": "Reload Submit Queue" + }, + { + "id": "3e6d4ffa", + "state": "SLEEPING", + "start_time": "2014-06-11 12:58:51.508000000", + "delay": 3287966, + "command": "Log File Compressor" + } + ] +---- + [[get-top-menus]] === Get Top Menus -- @@ -844,6 +903,27 @@ Summary about the JVM link:#jvm-summary-info[JvmSummaryInfo] entity. Only set if the `jvm` option was set. |============================ +[[task-info]] +=== TaskInfo +The `TaskInfo` entity contains information about a task in a background +work queue. + +[options="header",width="50%",cols="1,^1,5"] +|==================================== +|Field Name ||Description +|`id` ||The ID of the task. +|`state` || +The state of the task, can be `DONE`, `CANCELLED`, `RUNNING`, `READY`, +`SLEEPING` and `OTHER`. +|`start_time` ||The start time of the task. +|`delay` ||The remaining delay of the task. +|`command` ||The command of the task. +|`remote_name`|optional| +The remote name. May only be set for tasks that are associated with a +project. +|`project` |optional|The project the task is associated with. +|==================================== + [[task-summary-info]] === TaskSummaryInfo The `TaskSummaryInfo` entity contains information about the current diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTasks.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTasks.java new file mode 100644 index 0000000000..da5beda686 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTasks.java @@ -0,0 +1,132 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.common.collect.ComparisonChain; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.git.TaskInfoFactory; +import com.google.gerrit.server.git.WorkQueue; +import com.google.gerrit.server.git.WorkQueue.ProjectTask; +import com.google.gerrit.server.git.WorkQueue.Task; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectState; +import com.google.gerrit.server.util.IdGenerator; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Singleton +public class ListTasks implements RestReadView { + private final WorkQueue workQueue; + private final ProjectCache projectCache; + private final Provider self; + + @Inject + public ListTasks(WorkQueue workQueue, ProjectCache projectCache, + Provider self) { + this.workQueue = workQueue; + this.projectCache = projectCache; + this.self = self; + } + + @Override + public List apply(ConfigResource resource) throws AuthException { + CurrentUser user = self.get(); + if (!user.isIdentifiedUser()) { + throw new AuthException("Authentication required"); + } + + List allTasks = getTasks(); + if (user.getCapabilities().canViewQueue()) { + return allTasks; + } else { + Map visibilityCache = new HashMap<>(); + + List visibleTasks = new ArrayList<>(); + for (TaskInfo task : allTasks) { + if (task.projectName != null) { + Boolean visible = visibilityCache.get(task.projectName); + if (visible == null) { + ProjectState e = projectCache.get(new Project.NameKey(task.projectName)); + visible = e != null ? e.controlFor(user).isVisible() : false; + visibilityCache.put(task.projectName, visible); + } + if (visible) { + visibleTasks.add(task); + } + } + } + return visibleTasks; + } + } + + private List getTasks() { + List taskInfos = + workQueue.getTaskInfos(new TaskInfoFactory() { + @Override + public TaskInfo getTaskInfo(Task task) { + return new TaskInfo(task); + } + }); + Collections.sort(taskInfos, new Comparator() { + @Override + public int compare(TaskInfo a, TaskInfo b) { + return ComparisonChain.start() + .compare(a.state.ordinal(), b.state.ordinal()) + .compare(a.delay, b.delay) + .compare(a.command, b.command) + .result(); + } + }); + return taskInfos; + } + + public static class TaskInfo { + public String id; + public Task.State state; + public Timestamp startTime; + public long delay; + public String command; + public String remoteName; + public String projectName; + + public TaskInfo(Task task) { + this.id = IdGenerator.format(task.getTaskId()); + this.state = task.getState(); + this.startTime = new Timestamp(task.getStartTime().getTime()); + this.delay = task.getDelay(TimeUnit.MILLISECONDS); + this.command = task.toString(); + + if (task instanceof ProjectTask) { + ProjectTask projectTask = ((ProjectTask) task); + this.projectName = projectTask.getProjectNameKey().get(); + this.remoteName = projectTask.getRemoteName(); + } + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java index 91413052f6..772ad7fb10 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java @@ -16,6 +16,7 @@ package com.google.gerrit.server.config; import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND; import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND; +import static com.google.gerrit.server.config.TaskResource.TASK_KIND; import static com.google.gerrit.server.config.TopMenuResource.TOP_MENU_KIND; import com.google.gerrit.extensions.registration.DynamicMap; @@ -26,8 +27,10 @@ public class Module extends RestApiModule { protected void configure() { DynamicMap.mapOf(binder(), CAPABILITY_KIND); DynamicMap.mapOf(binder(), CONFIG_KIND); + DynamicMap.mapOf(binder(), TASK_KIND); DynamicMap.mapOf(binder(), TOP_MENU_KIND); child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class); + child(CONFIG_KIND, "tasks").to(TasksCollection.class); child(CONFIG_KIND, "top-menus").to(TopMenuCollection.class); get(CONFIG_KIND, "version").to(GetVersion.class); get(CONFIG_KIND, "preferences").to(GetPreferences.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/TaskResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/TaskResource.java new file mode 100644 index 0000000000..5db163ddcf --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/TaskResource.java @@ -0,0 +1,23 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.gerrit.extensions.restapi.RestView; +import com.google.inject.TypeLiteral; + +public class TaskResource extends ConfigResource { + public static final TypeLiteral> TASK_KIND = + new TypeLiteral>() {}; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/TasksCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/TasksCollection.java new file mode 100644 index 0000000000..98dd92139d --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/TasksCollection.java @@ -0,0 +1,53 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.ChildCollection; +import com.google.gerrit.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class TasksCollection implements + ChildCollection { + private final DynamicMap> views; + private final ListTasks list; + + @Inject + TasksCollection(DynamicMap> views, + ListTasks list) { + this.views = views; + this.list = list; + } + + @Override + public RestView list() { + return list; + } + + @Override + public TaskResource parse(ConfigResource parent, IdString id) + throws ResourceNotFoundException { + throw new ResourceNotFoundException(id); + } + + @Override + public DynamicMap> views() { + return views; + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java index 77d79b3001..147d52a79c 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java @@ -17,15 +17,12 @@ package com.google.gerrit.sshd.commands; import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE; import com.google.common.base.Objects; -import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.git.TaskInfoFactory; -import com.google.gerrit.server.git.WorkQueue; -import com.google.gerrit.server.git.WorkQueue.ProjectTask; +import com.google.gerrit.server.config.ConfigResource; +import com.google.gerrit.server.config.ListTasks; +import com.google.gerrit.server.config.ListTasks.TaskInfo; import com.google.gerrit.server.git.WorkQueue.Task; -import com.google.gerrit.server.project.ProjectCache; -import com.google.gerrit.server.project.ProjectState; -import com.google.gerrit.server.util.IdGenerator; import com.google.gerrit.server.util.TimeUtil; import com.google.gerrit.sshd.AdminHighPriorityCommand; import com.google.gerrit.sshd.CommandMetaData; @@ -37,11 +34,8 @@ import org.kohsuke.args4j.Option; import java.io.IOException; import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; /** Display the current work queue. */ @AdminHighPriorityCommand @@ -52,10 +46,7 @@ final class ShowQueue extends SshCommand { private boolean wide; @Inject - private WorkQueue workQueue; - - @Inject - private ProjectCache projectCache; + private ListTasks listTasks; @Inject private IdentifiedUser currentUser; @@ -64,7 +55,7 @@ final class ShowQueue extends SshCommand { private int taskNameWidth; @Override - public void start(final Environment env) throws IOException { + public void start(Environment env) throws IOException { String s = env.getEnv().get(Environment.ENV_COLUMNS); if (s != null && !s.isEmpty()) { try { @@ -77,125 +68,59 @@ final class ShowQueue extends SshCommand { } @Override - protected void run() { + protected void run() throws UnloggedFailure { taskNameWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4; - final List pending = getSortedTaskInfoList(); - stdout.print(String.format("%-8s %-12s %-12s %-4s %s\n", // "Task", "State", "StartTime", "", "Command")); stdout.print("----------------------------------------------" + "--------------------------------\n"); - int numberOfPendingTasks = 0; - final long now = TimeUtil.nowMs(); - final boolean viewAll = currentUser.getCapabilities().canViewQueue(); - - for (final QueueTaskInfo taskInfo : pending) { - final long delay = taskInfo.delayMillis; - final Task.State state = taskInfo.state; - - final String start; - switch (state) { - case DONE: - case CANCELLED: - case RUNNING: - case READY: - start = format(state); - break; - default: - start = time(now, delay); - break; - } - - boolean regularUserCanSee = false; - boolean hasCustomizedPrint = true; - - // If the user is not administrator, check if has rights to see - // the Task - Project.NameKey projectName = null; - String remoteName = null; - - if (!viewAll) { - projectName = taskInfo.getProjectNameKey(); - remoteName = taskInfo.getRemoteName(); - hasCustomizedPrint = taskInfo.hasCustomizedPrint(); - - ProjectState e = null; - if (projectName != null) { - e = projectCache.get(projectName); + try { + List tasks = listTasks.apply(new ConfigResource()); + long now = TimeUtil.nowMs(); + boolean viewAll = currentUser.getCapabilities().canViewQueue(); + for (TaskInfo task : tasks) { + String start; + switch (task.state) { + case DONE: + case CANCELLED: + case RUNNING: + case READY: + start = format(task.state); + break; + default: + start = time(now, task.delay); + break; } - regularUserCanSee = e != null && e.controlFor(currentUser).isVisible(); + // Shows information about tasks depending on the user rights + if (viewAll || task.projectName == null) { + String command = task.command.length() < taskNameWidth + ? task.command + : task.command.substring(0, taskNameWidth); - if (regularUserCanSee) { - numberOfPendingTasks++; + stdout.print(String.format("%8s %-12s %-12s %-4s %s\n", + task.id, start, startTime(task.startTime), "", command)); + } else { + String remoteName = task.remoteName != null + ? task.remoteName + "/" + task.projectName + : task.projectName; + + stdout.print(String.format("%8s %-12s %-4s %s\n", + task.id, start, startTime(task.startTime), + Objects.firstNonNull(remoteName, "n/a"))); } } - - String startTime = startTime(taskInfo.getStartTime()); - - // Shows information about tasks depending on the user rights - if (viewAll || (!hasCustomizedPrint && regularUserCanSee)) { - stdout.print(String.format("%8s %-12s %-12s %-4s %s\n", // - id(taskInfo.getTaskId()), start, startTime, "", - taskInfo.getTaskString(taskNameWidth))); - } else if (regularUserCanSee) { - if (projectName != null) { - if (remoteName == null) { - remoteName = projectName.get(); - } else { - remoteName = remoteName + "/" + projectName.get(); - } - } - - stdout.print(String.format("%8s %-12s %-4s %s\n", - id(taskInfo.getTaskId()), start, startTime, - Objects.firstNonNull(remoteName, "n/a"))); - } + stdout.print("----------------------------------------------" + + "--------------------------------\n"); + stdout.print(" " + tasks.size() + " tasks\n"); + } catch (AuthException e) { + throw die(e); } - stdout.print("----------------------------------------------" - + "--------------------------------\n"); - - if (viewAll) { - numberOfPendingTasks = pending.size(); - } - - stdout.print(" " + numberOfPendingTasks + " tasks\n"); } - private List getSortedTaskInfoList() { - final List taskInfos = - workQueue.getTaskInfos(new TaskInfoFactory() { - @Override - public QueueTaskInfo getTaskInfo(Task task) { - return new QueueTaskInfo(task); - } - }); - Collections.sort(taskInfos, new Comparator() { - @Override - public int compare(QueueTaskInfo a, QueueTaskInfo b) { - if (a.state != b.state) { - return a.state.ordinal() - b.state.ordinal(); - } - - int cmp = Long.signum(a.delayMillis - b.delayMillis); - if (cmp != 0) { - return cmp; - } - - return a.getTaskString(taskNameWidth) - .compareTo(b.getTaskString(taskNameWidth)); - } - }); - return taskInfos; - } - - private static String id(final int id) { - return IdGenerator.format(id); - } - - private static String time(final long now, final long delay) { - final Date when = new Date(now + delay); + private static String time(long now, long delay) { + Date when = new Date(now + delay); return format(when, delay); } @@ -203,14 +128,14 @@ final class ShowQueue extends SshCommand { return format(when, TimeUtil.nowMs() - when.getTime()); } - private static String format(final Date when, final long timeFromNow) { + private static String format(Date when, long timeFromNow) { if (timeFromNow < 24 * 60 * 60 * 1000L) { return new SimpleDateFormat("HH:mm:ss.SSS").format(when); } return new SimpleDateFormat("MMM-dd HH:mm").format(when); } - private static String format(final Task.State state) { + private static String format(Task.State state) { switch (state) { case DONE: return "....... done"; @@ -226,50 +151,4 @@ final class ShowQueue extends SshCommand { return state.toString(); } } - - private static class QueueTaskInfo { - private final long delayMillis; - private final Task.State state; - private final Task task; - - QueueTaskInfo(Task task) { - this.task = task; - this.delayMillis = task.getDelay(TimeUnit.MILLISECONDS); - this.state = task.getState(); - } - - String getRemoteName() { - if (task instanceof ProjectTask) { - return ((ProjectTask) task).getRemoteName(); - } - return null; - } - - Project.NameKey getProjectNameKey() { - if (task instanceof ProjectTask) { - return ((ProjectTask) task).getProjectNameKey(); - } - return null; - } - - boolean hasCustomizedPrint() { - if (task instanceof ProjectTask) { - return ((ProjectTask) task).hasCustomizedPrint(); - } - return false; - } - - int getTaskId() { - return task.getTaskId(); - } - - Date getStartTime() { - return task.getStartTime(); - } - - String getTaskString(int maxLength) { - String s = task.toString(); - return s.length() < maxLength ? s : s.substring(0, maxLength); - } - } }