Git Product home page Git Product logo

sparen-dnh-apm's Introduction

Sparen's Danmakufu Application Monitoring System

Welcome to the repository for Sparen's Danmakufu Application Monitoring System! This system aims to track statistics over the lifetime of a script, outputting a report on request.

This software is capable of profiling a single run of a script and monitoring behavior as well as aggregating data over multiple runs of a script, providing average case data as well as other visualizations.

Usage

Setup - @Loading

To use the APM, you will first need to find a Stage Script (or Plural or Single) to monitor or profile.

In @Loading for the script of your choice, do the following:

@Loading {
    let apm = LoadScript(GetCurrentScriptDirectory() ~ "./../SparenDNHAPM.dnh");
    //Set Common Data
    StartScript(apm);
}

Adjust the parameter of LoadScript() to the path to SparenDNHAPM.dnh. Once you've set all of the CommonData, run StartScript() to begin the APM script.

Example below:

@Loading {
    let apm = LoadScript(GetCurrentScriptDirectory() ~ "./../../SparenDNHAPM.dnh");
    SetAreaCommonData("dnhapm", "OutputDirectory", GetCurrentScriptDirectory() ~ "./../dnhapm-output/");
    SetAreaCommonData("dnhapm", "ScriptName", "Moonstruck Magician");
    SetAreaCommonData("dnhapm", "GraphScale", 3);
    SetAreaCommonData("dnhapm", "GrazeUpdateFreq", 6);
    SetAreaCommonData("dnhapm", "UserNoteSlots", 3);
    StartScript(apm);
}

Setup - Report Generation

At the end of your script (before it closes on regular termination/player death), you will need to notify the APM that it needs to generate its report. To do this, use NotifyEventAll. By default, the APM uses EV_USER_STAGE + 2048 and will output to GetCurrentScriptDirectory() ~ "./dnhapm-output/".

At least four frames of yielding are required between calling the event and closing the stage scene. This is because DNHAPM requires multiple frames to store data and generate the report. For futureproofing in case this value increases, it is recommended that you yield at least 6 frames before closing your stage scene. If you are running DNHAPM via a Single script that closes itself via CloseScript(GetOwnScriptID()), it seems that the APM will continue running until completion, so no yield frames are needed, but it is recommended that you provide them anyways just as a precaution.

In addition, note that you can only write a report ONCE per run of a script. This is because when the event to generate the report is called, all logging tasks stop logging and write to file. These tasks are not restarted afterwards, meaning that if you were to call the event more than one time in a single script, later calls would overwrite the original report with the contents of the original report (i.e. no change), since no new data has been logged.

Example usage of the event call to trigger report generation below:

task StageOver {
    while(!finished && GetPlayerState() != STATE_END){yield;}
    NotifyEventAll(EV_USER_STAGE + 2048, "");
    loop(60){yield;} // At least four frames are required between calling the event and closing the stage.
    ...
    CloseStgScene();
}

Modified CSV files as well as HTML files will be generated in the target directory. The CSV files, if kept, will allow for successive runs of the APM to log past data should aggregation be enabled. The HTML files contain the reports.

Usage - Other Notes

This script works when saved as part of a replay, but replays made before the profiler was implemented will desync when run after the profiler has been added.

Typically, DNHAPM only needs to run in one script. If you wish to transition to a production or release environment after using the APM, all you need to do is block comment out the setup in @Loading, as the only other code needed to run DNHAPM will be Event calls in other scripts, which have minimal overhead.

For usage examples, refer to test/*.

Customization

The APM uses CommonData in the "dnhapm" area for configuration. To set configuration, SetAreaCommonData("dnhapm", "FieldToAdjust", New Value); in @Loading between LoadScript(), which initializes the Common Data Area, and StartScript(), which runs the script with the specified parameters.

Basic Configuration

EVENT_DNHAPM_FINALIZE = GetAreaCommonData("dnhapm", "FinalizeEvent", EV_USER_STAGE + 2048);
OUTPUT_DIRECTORY = GetAreaCommonData("dnhapm", "OutputDirectory", GetCurrentScriptDirectory() ~ "./dnhapm-output/");
SCRIPT_NAME = GetAreaCommonData("dnhapm", "ScriptName", "--Unnamed Script--");
OUTPUT_NAME = GetAreaCommonData("dnhapm", "OutputName", SCRIPT_NAME ~ "_" ~ player);
ENABLE_AGGREGATE = GetAreaCommonData("dnhapm", "EnableAggregate", true);
GRAPH_SCALE = GetAreaCommonData("dnhapm", "GraphScale", 2);
AGGREGATE_LINE_OPACITY = GetAreaCommonData("dnhapm", "AggregateLineOpacity", 0.25);

Basic configuration for the APM allows you to set the Event that is used to trigger writing the report (defaults to EV_USER_STAGE + 2048), the output directory of the report (defaults to GetCurrentScriptDirectory() ~ "./dnhapm-output/"), and the name of the script within the report (defaults to "--Unnamed Script--").

Aggregate, if enabled, will load past data in OUTPUT_DIRECTORY/apmcsv/ into the graphs (defaults to true). The Output Name field refers to the unique identifier for the script and is used in output file filenames - important for the aggregation feature. For example, you could use SetAreaCommonData("dnhapm", "OutputName", SCRIPT_NAME) to have all past runs of SCRIPT_NAME aggregate into the same file regardless of which player did the run, or you could choose to have different difficulties aggregate independently while sharing the same output folder for all reports.

GRAPH_SCALE is the scale of the x axis of the graph (defaults to 2, or two pixel per second). This means that a 5 minute script will fit into 300 pixels at a scale of 1, or 600 pixels at a scale of 2 (default).

In regards to aggregate data, this program assumes that the settings for prior runs were the same (i.e. it assumes that update frequencies, graph scale, etc. were the same as the current run). Former runs will appear with opacity equal to AGGREGATE_LINE_OPACITY (defaults to alpha of 0.25).

It is recommended that you use a different output directory for each script to monitor. However, the APM supports using one directory for different scripts, as long as OUTPUT_NAME has been set. If not set while aggregation is enabled, data from other scripts may make its way into the report.

Specific Configuration

By default, the APM will log everything specified, but there are Common Data to disable these features. Within each feature set, there are a number of different fields that can be adjusted.

This section will be populated with data as new features are implemented.

FPS Monitoring


Sample FPS graph, showing lag spikes at plural loading and spellcard declaration

FEATURE_FPS_ENABLE = GetAreaCommonData("dnhapm", "FPSEnable", true);
FEATURE_FPS_UPDATEFREQ = GetAreaCommonData("dnhapm", "FPSUpdateFreq", 10);
FEATURE_FPS_LINECOLOR = GetAreaCommonData("dnhapm", "FPSLineColor", "#2266FF");
FEATURE_FPS_ENABLE_LINE = GetAreaCommonData("dnhapm", "FPSEnableLine", true);
FEATURE_FPS_MODE = GetAreaCommonData("dnhapm", "FPSMode", "MODE_CURRENTFPS");
FEATURE_FPS_CORRECTIONFACTOR = GetAreaCommonData("dnhapm", "FPSCorrFactor", 1.0325);

For FPS Monitoring, there are three main fields - one for enabling the feature, one for the update frequency (defaults to every 10 frames), and one for the line color in the graphs (defaults to #2266FF).

As of July 6, 2018, there are two different options for FPS Logging. One is the original (default) GetCurrentFps method (use "MODE_CURRENTFPS"). The other option is "MODE_STAGETIME", implemented by Drake. The former uses Danmakufu's built-in system and updates infrequently due to Danmakufu's built-in FPS update speed, while the latter can be noisy and is always a little off of the actual FPS, requiring a correction factor that may depend on the computer Danmakufu is being run on. This correction factor defaults to 1.0325. Refer to the now-closed Pull Request #1 for more details.

FPS monitoring is approximately half a pixel (default scaling) off from the rest of the monitoring fields. There is also a dashed line at 60 FPS, which can be disabled via the FPSEnableLine CommonData.

Graze Monitoring

FEATURE_GRAZE_ENABLE = GetAreaCommonData("dnhapm", "GrazeEnable", true);
FEATURE_GRAZE_UPDATEFREQ = GetAreaCommonData("dnhapm", "GrazeUpdateFreq", 10);
FEATURE_GRAZE_LINECOLOR = GetAreaCommonData("dnhapm", "GrazeLineColor", "#887777");

For Graze Monitoring, similarly to FPS Monitoring, there are three primary fields - one for enabling the feature, one for the update frequency (defaults to every 10 frames), and one for the line color in the graphs (defaults to #887777).

For Graze Monitoring, note that the default update frequency will result in a less smooth chart, but should nevertheless provide a good approximation for longer scripts.

Score Monitoring

FEATURE_SCORE_ENABLE = GetAreaCommonData("dnhapm", "ScoreEnable", true);
FEATURE_SCORE_UPDATEFREQ = GetAreaCommonData("dnhapm", "ScoreUpdateFreq", 10);
FEATURE_SCORE_LINECOLOR = GetAreaCommonData("dnhapm", "ScoreLineColor", "#FF5555");

For Score Monitoring, similarly to FPS and Graze Monitoring, there are three fields - one for enabling the feature, one for the update frequency (defaults to every 10 frames), and one for the line color in the graphs (defaults to #FF5555).

For Score Monitoring, like Graze Monitoring, note that the default update frequency will result in a less smooth chart, but should nevertheless provide a good approximation for longer scripts.

In addition, note that score is often player dependent and this metric is therefore primarily applicable in games with a proper scoring system.

Enemy Shot Count Monitoring

FEATURE_SHOTCOUNT_ENABLE = GetAreaCommonData("dnhapm", "ShotCountEnable", true);
FEATURE_SHOTCOUNT_UPDATEFREQ = GetAreaCommonData("dnhapm", "ShotCountUpdateFreq", 10);
FEATURE_SHOTCOUNT_LINECOLOR = GetAreaCommonData("dnhapm", "ShotCountLineColor", "#FFCC55");

For Shot Count Monitoring, similarly to the other monitoring systems, there are three primary fields - one for enabling the feature, one for the update frequency (defaults to every 10 frames), and one for the line color in the graphs (defaults to #FFCC55).

For Shot Count Monitoring, like Graze and Score Monitoring, note that the default update frequency will result in a less smooth chart, but should nevertheless provide a good approximation for longer scripts.

Death & Bomb Monitoring

FEATURE_BOMBDEATH_ENABLE = GetAreaCommonData("dnhapm", "BombDeathEnable", true);
FEATURE_BOMBDEATH_BOMBCOLOR = GetAreaCommonData("dnhapm", "BombCircleColor", "#44DD44");
FEATURE_BOMBDEATH_DEATHCOLOR = GetAreaCommonData("dnhapm", "DeathCircleColor", "#DD44DD");
FEATURE_BOMBDEATH_MAINBULLETSIZE = GetAreaCommonData("dnhapm", "BDMainBulletSize", 8);
FEATURE_BOMBDEATH_SUBBULLETSIZE = GetAreaCommonData("dnhapm", "BDSubBulletSize", 2);
FEATURE_BOMBDEATH_TEXTSIZE = GetAreaCommonData("dnhapm", "BDBulletTextSize", 9);

For Death & Bomb Monitoring, similarly to the other monitoring systems, there is a field for enabling the feature. There is no field for update frequency since this monitoring system listens to EV_PLAYER_SPELL and EV_PLAYER_SHOOTDOWN rather than constantly logging data. There are two fields for the color in the graphs (defaults to #44DD44 for bombs and #DD44DD for deaths).

With Death & Bomb Monitoring, deaths and bombs appear as circles ('bullets') on the graph. Those in the current run have their radius default to 8, with 9px white font above them ('b' for bomb, 'd' for death). Aggregate data is rendered at a seperate location from the current run's data, and these circles have their radius default to 2. Since data from other runs is rendered at a seperate location, they do not currently render with opacity, though this may be changed if requested by end-users.

User-Defined Monitoring

In SparenDNHAPM_userdef.dnh, it is possible to define your own monitoring fields so that they can be monitored as well. This works best with things such as game-specific item drops controlled by CommonData, Power systems, and other mechanics that are not standardized enough to warrant a built-in solution.

The file itself is #included by the main script and there are directions inside the file detailing what must be done. For the most part, all that needs to be done is to fill in the contents of the three functions (APM_StartUserLogging, APM_LogUserFieldToTOC, and APM_LogDataToFile), and adjust the APM_Start* and APM_ChartGen* tasks/functions to log your field, with your defined settings.

Report Generating

The report generated by the APM by default provides the start time of data recording as well as the player. In addition, arbitrary text can be inserted into the first part of the report with USER_TEXT.

Use SetAreaCommonData("dnhapm", "UserText", ""); to set the user text, which should be in proper HTML. You could technically override the existing CSS styles and inject JavaScript into the HTML body this way, but I advise against it.

Notes

The system also allows for an arbitrary number of user notes. These user notes are per-run and only reflect the last run of the script.

USER_NOTE_SLOTS = GetAreaCommonData("dnhapm", "UserNoteSlots", 2);
EVENT_USER_NOTE = GetAreaCommonData("dnhapm", "UserNoteEvent", EV_USER_STAGE + 2050);
ENABLE_NOTE_LINE = GetAreaCommonData("dnhapm", "EnableNoteLine", true);

There are 2 note slots by default, and notes render at the bottom of the graph. By default, EV_USER_STAGE + 2050 is used to write notes to the monitoring system.

For example:

NotifyEventAll(EV_USER_STAGE + 2050, ["0", "Start"]);

This writes the "Start" note to note slot 0 (note slots are zero-indexed, and are passed as strings rather than integers).

Another example, for making note of when certain attacks start - in this case a spellcard:

@Initialize {
    objBoss = ObjEnemy_Create(OBJ_ENEMY_BOSS);
    ObjEnemy_Regist(objBoss);
    CutIn(objBoss, "KORO", "Magic Sign \"The Glowing Waves of the Moon and Stars (N)\"", 5, BossCutIn, 0, 1, 384, 384);
    NotifyEventAll(EV_USER_STAGE + 2050, ["1", "S3N"]);
    ObjEnemyBossScene_StartSpell(objScene);
    ...
}

When using the note system, please be careful of text length. If your graph scale is small, if your notes are large, and/or if your notes are closely spaced together, text may overlap and become hard to read. It may therefore be helpful to abbreviate your notes (e.g. "Midboss Spell 2" - "MS2", etc.).

By default, all notes have a line dropped down from the x axis to their height for easy identification. The ENABLE_NOTE_LINE field adds another line up from the x axis to the top of the graph (in a lighter tone) for easier distinction where a note was placed. Depending on how you use the APM's notes, this may allow you to chunk the graph into 'phases' for easier analysis.

Steps


Sample graph with default Step Logging settings, showing combined usage of Notes and Step Logging

The APM also has the capability to log steps in Plural Scripts via EV_START_BOSS_STEP, EV_END_BOSS_STEP, and EV_START_BOSS_SPELL. Like with notes, these are per-run and only reflect the last run of the script. Together with notes, it becomes possible to see which attacks started at what time and how long they lasted.

Step Logging comes with configuration settings like all of the other options.

ENABLE_STEP = GetAreaCommonData("dnhapm", "EnableStep", true);
STEP_NONSPELLCOLOR = GetAreaCommonData("dnhapm", "StepNonspellColor", "#CACACA");
STEP_SPELLCOLOR = GetAreaCommonData("dnhapm", "StepSpellColor", "#DDBBDD");
ENABLE_STEP_LINE = GetAreaCommonData("dnhapm", "EnableStepLine", true);

First is the field for enabling and disabling the feature. After this come two color fields - one for the background color of nonspell blocks (defaults to #CACACA) and the other for spell blocks (defaults to #DDBBDD). Note that the blocks are rendered with opacity 0.1, so the colors will appear faint, but hopefully visibly enough that nonspell and spell steps can be distinguished from each other as well as portions outside of boss battles.

Finally, there is an option to enable/disable a line at the top of each step block. This line is in the specified color and can be used for clearer determination between different steps.

Current and Planned Features

Current:

  • FPS Monitoring
  • Graze Monitoring
  • Score Minitoring
  • Enemy Shot Count Minitoring
  • Death and Bomb Monitoring
  • User Notes
  • Step Monitoring (Nonspell and Spellcard Graph Overlays)

Planned:

  • Combined graphs (stretch goal)

Downloads

Official Releases are provided via GitHub releases.

Contributing and Credits

Improvements and bug reports should be submitted via GitHub issues. Pull requests will be reviewed.

sparen-dnh-apm's People

Contributors

drakeirving avatar sparen avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

drakeirving

sparen-dnh-apm's Issues

Implement Config Option for loading notes

This issue covers the two following features:

  • Logging notes to file the same way as spells and bombs
  • Viewing old notes at lower opacity (config to enable/disable, with disable rendering current behavior and being the default)

This addresses the issue of variable length runs but adds clutter and complicates an otherwise simple note implementation.

This feature request is very low priority and may not be implemented at all depending on user feedback.

Implement csv lookup by user defined ID string

Currently, all csv files with apm<string>- are assumed to be for the given script regardless of whether or not that was the actual case. It is therefore preferable to check the user-defined script name as well (but not the player) so that multiple scripts can output csv files to the same directory.

This may be more complex than initially anticipated due to the mechanics behind Danmakufu's string indexing and comparison.

Implement Bomb/Death bullet size configuration

Currently there is no way to configure the size of the bullets and text in the bomb/death graph. This issue covers the creation and implementation of configuration for these. There will likely be three fields - one for the main bullet, one for the aggregate bullet, and one for the text. Former two are higher priority.

Implement User-Defined Monitoring

This issue covers the implementation of User-Defined Monitoring.

User-Defined Monitoring enables scripters to provide custom hook-ins for their own metrics. For example, tracking Piezo Shards in SeitenTouji AMB, or the Discord meter in EUB. Since these are game specific, it is not possible to implement them locally. Instead, it is necessary to provide an extendable interface for monitoring.

This will most likely involve a required #include file that provides empty methods for the scripter to fill in.

Implement Death and Bomb Monitoring

This issue covers the implementation of death and bomb monitoring (including writing to file and reading past data).

Similarly to notes and different to existing forms of monitoring, Death and Bomb monitoring is logged via Events rather than constantly calling a function [EV_PLAYER_SPELL and EV_PLAYER_SHOOTDOWN]. Preferably, both death and bomb statistics would be tracked in the same file as well.

This feature also requires a new type of graph that paints dots onto a time graph rather than lines, as death and bomb events are discrete rather than continuous.

Implement Graph Overlays via Step Monitoring

This issue covers the utilization of EV_START_BOSS_STEP and EV_END_BOSS_STEP listeners to determine the boss steps in the current run for the purposes of providing an overlay.

As this may be a complement rather than a replacement for annotated notes, there may be no need to distinguish spells and non spells by color, as there may be a note that will divide the block visually. Otherwise, if no notes are used, the entire boss plural may blur together.

The graph overlay should be done via opacity rather than a hard color.

Determine post-report generation safety frame count

This issue covers the determining of how many frames of padding are needed between the event to generate a report and closing the stage script to ensure that the report is actually generated.

In the event that this is non-zero, the value MUST be documented within the README and preferably in the script header as well.

Bomb & Death monitoring doesn't know max time

Currently, bomb and death monitoring does not have access to the max time - only the time of the last death. This results in the graph being noticeably shorter than the others, especially due to text being cut off.

To fix, the output file should have a timestamp of the max time.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.